Add infrastructure for copy/paste and DND of rich text for GtkTextBuffer.
authorMichael Natterer <mitch@imendio.com>
Tue, 7 Mar 2006 13:46:11 +0000 (13:46 +0000)
committerMichael Natterer <mitch@src.gnome.org>
Tue, 7 Mar 2006 13:46:11 +0000 (13:46 +0000)
2006-03-07  Michael Natterer  <mitch@imendio.com>

Add infrastructure for copy/paste and DND of rich text for
GtkTextBuffer. Fixes bug #324177.

* gtk/gtktextbufferrichtext.[ch]: new files implementing a
per-buffer registry of rich text formats.

* gtk/gtk.h: #include gtktextbufferrichtext.h

* gtk/gtktextbufferserialize.[ch]: new files implementing an
internal serialization format that can handle all of a text
buffer's tags and pixbufs. It's not useful for anything except
tranfer between instances of GtkTextBuffer (Anders Carlsson).

* gtk/Makefile.am: build the new files.

* gtk/gtkclipboard.[ch]: added convenience APIs for rich text,
just as they exist for plain text and pixbufs.

* gtk/gtkselection.[ch]: added rich text convenience APIs here
too.  Return the target list from gtk_target_list_ref(). Register
GtkTargetList as boxed type. Added
gtk_target_table_new_from_list() and gtk_target_table_free(),
which make converting between GtkTargetList and arrays of
GtkTargetEntry considerably easier.

* gtk/gtktextutil.[ch]: added _gtk_text_util_create_rich_drag_icon()
which creates a fancy rich text icon (Matthias Clasen).

* gtk/gtktextbuffer.[ch]: use all the new stuff above and
implement copy and paste of rich text. Added APIs for getting the
target lists used for copy and paste. Added public enum
GtkTextBufferTargetInfo which contains the "info" IDs associated
with the entries of the target lists.

* gtk/gtktextview.c: use the new rich text APIs and
GtkTextBuffer's new target list API to enable DND of rich text
chunks.

* gtk/gtk.symbols: export all the new symbols added.

* tests/testtext.c: added rich text testing stuff.

19 files changed:
ChangeLog
ChangeLog.pre-2-10
gtk/Makefile.am
gtk/gtk.h
gtk/gtk.symbols
gtk/gtkclipboard.c
gtk/gtkclipboard.h
gtk/gtkselection.c
gtk/gtkselection.h
gtk/gtktextbuffer.c
gtk/gtktextbuffer.h
gtk/gtktextbufferrichtext.c [new file with mode: 0644]
gtk/gtktextbufferrichtext.h [new file with mode: 0644]
gtk/gtktextbufferserialize.c [new file with mode: 0644]
gtk/gtktextbufferserialize.h [new file with mode: 0644]
gtk/gtktextutil.c
gtk/gtktextutil.h
gtk/gtktextview.c
tests/testtext.c

index 93f5f7e8b2033e7bc3d6ec403d8ef2a49b61c640..44cfdcfa49bcbfe6932317996611ab341ec34703 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,47 @@
+2006-03-07  Michael Natterer  <mitch@imendio.com>
+
+       Add infrastructure for copy/paste and DND of rich text for
+       GtkTextBuffer. Fixes bug #324177.
+
+       * gtk/gtktextbufferrichtext.[ch]: new files implementing a
+       per-buffer registry of rich text formats.
+
+       * gtk/gtk.h: #include gtktextbufferrichtext.h
+
+       * gtk/gtktextbufferserialize.[ch]: new files implementing an
+       internal serialization format that can handle all of a text
+       buffer's tags and pixbufs. It's not useful for anything except
+       tranfer between instances of GtkTextBuffer (Anders Carlsson).
+
+       * gtk/Makefile.am: build the new files.
+
+       * gtk/gtkclipboard.[ch]: added convenience APIs for rich text,
+       just as they exist for plain text and pixbufs.
+
+       * gtk/gtkselection.[ch]: added rich text convenience APIs here
+       too.  Return the target list from gtk_target_list_ref(). Register
+       GtkTargetList as boxed type. Added
+       gtk_target_table_new_from_list() and gtk_target_table_free(),
+       which make converting between GtkTargetList and arrays of
+       GtkTargetEntry considerably easier.
+
+       * gtk/gtktextutil.[ch]: added _gtk_text_util_create_rich_drag_icon()
+       which creates a fancy rich text icon (Matthias Clasen).
+
+       * gtk/gtktextbuffer.[ch]: use all the new stuff above and
+       implement copy and paste of rich text. Added APIs for getting the
+       target lists used for copy and paste. Added public enum
+       GtkTextBufferTargetInfo which contains the "info" IDs associated
+       with the entries of the target lists.
+
+       * gtk/gtktextview.c: use the new rich text APIs and
+       GtkTextBuffer's new target list API to enable DND of rich text
+       chunks.
+
+       * gtk/gtk.symbols: export all the new symbols added.
+
+       * tests/testtext.c: added rich text testing stuff.
+
 2006-03-06  Matthias Clasen  <mclasen@redhat.com>
 
        * gtk/gtktextview.c (text_window_invalidate_cursors): Take
index 93f5f7e8b2033e7bc3d6ec403d8ef2a49b61c640..44cfdcfa49bcbfe6932317996611ab341ec34703 100644 (file)
@@ -1,3 +1,47 @@
+2006-03-07  Michael Natterer  <mitch@imendio.com>
+
+       Add infrastructure for copy/paste and DND of rich text for
+       GtkTextBuffer. Fixes bug #324177.
+
+       * gtk/gtktextbufferrichtext.[ch]: new files implementing a
+       per-buffer registry of rich text formats.
+
+       * gtk/gtk.h: #include gtktextbufferrichtext.h
+
+       * gtk/gtktextbufferserialize.[ch]: new files implementing an
+       internal serialization format that can handle all of a text
+       buffer's tags and pixbufs. It's not useful for anything except
+       tranfer between instances of GtkTextBuffer (Anders Carlsson).
+
+       * gtk/Makefile.am: build the new files.
+
+       * gtk/gtkclipboard.[ch]: added convenience APIs for rich text,
+       just as they exist for plain text and pixbufs.
+
+       * gtk/gtkselection.[ch]: added rich text convenience APIs here
+       too.  Return the target list from gtk_target_list_ref(). Register
+       GtkTargetList as boxed type. Added
+       gtk_target_table_new_from_list() and gtk_target_table_free(),
+       which make converting between GtkTargetList and arrays of
+       GtkTargetEntry considerably easier.
+
+       * gtk/gtktextutil.[ch]: added _gtk_text_util_create_rich_drag_icon()
+       which creates a fancy rich text icon (Matthias Clasen).
+
+       * gtk/gtktextbuffer.[ch]: use all the new stuff above and
+       implement copy and paste of rich text. Added APIs for getting the
+       target lists used for copy and paste. Added public enum
+       GtkTextBufferTargetInfo which contains the "info" IDs associated
+       with the entries of the target lists.
+
+       * gtk/gtktextview.c: use the new rich text APIs and
+       GtkTextBuffer's new target list API to enable DND of rich text
+       chunks.
+
+       * gtk/gtk.symbols: export all the new symbols added.
+
+       * tests/testtext.c: added rich text testing stuff.
+
 2006-03-06  Matthias Clasen  <mclasen@redhat.com>
 
        * gtk/gtktextview.c (text_window_invalidate_cursors): Take
index 8653b58708e0b8f0a6f7af4ab76647552abb2261..84d41c83ebeebab2912169db1427f010bf1f5755 100644 (file)
@@ -248,6 +248,7 @@ gtk_public_h_sources =          \
        gtktearoffmenuitem.h    \
        gtktext.h               \
        gtktextbuffer.h         \
+       gtktextbufferrichtext.h \
        gtktextchild.h          \
        gtktextdisplay.h        \
        gtktextiter.h           \
@@ -481,6 +482,9 @@ gtk_c_sources =                 \
        gtktext.c               \
        gtktextbtree.c          \
        gtktextbuffer.c         \
+       gtktextbufferrichtext.c \
+       gtktextbufferserialize.c\
+       gtktextbufferserialize.h\
        gtktextchild.c          \
        gtktextdisplay.c        \
        gtktextiter.c           \
index f7bad3354d250a101a82a50d0125e9353631726c..54b4d67c444b5b90ddc4c5d6baa2e2d916ec8520 100644 (file)
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
 #include <gtk/gtktearoffmenuitem.h>
 #include <gtk/gtktext.h>
 #include <gtk/gtktextbuffer.h>
+#include <gtk/gtktextbufferrichtext.h>
 #include <gtk/gtktextview.h>
 #include <gtk/gtktipsquery.h>
 #include <gtk/gtktoggleaction.h>
index 570eaf9da6d44fa24e430669c1cff6e6bd9a55e7..f8b4edb8bcafd3fbf381742e9323e719800074a3 100644 (file)
@@ -600,6 +600,7 @@ gtk_clipboard_get_owner
 gtk_clipboard_get_type G_GNUC_CONST
 gtk_clipboard_request_contents
 gtk_clipboard_request_image
+gtk_clipboard_request_rich_text
 gtk_clipboard_request_targets
 gtk_clipboard_request_text
 gtk_clipboard_set_can_store
@@ -610,9 +611,11 @@ gtk_clipboard_set_with_owner
 gtk_clipboard_store
 gtk_clipboard_wait_for_contents
 gtk_clipboard_wait_for_image
+gtk_clipboard_wait_for_rich_text
 gtk_clipboard_wait_for_targets
 gtk_clipboard_wait_for_text
 gtk_clipboard_wait_is_image_available
+gtk_clipboard_wait_is_rich_text_available
 gtk_clipboard_wait_is_text_available
 gtk_clipboard_wait_is_target_available
 #endif
@@ -2607,6 +2610,23 @@ gtk_rc_style_unref
 #endif
 #endif
 
+#if IN_HEADER(__GTK_TEXT_BUFFER_RICH_TEXT_H__)
+#if IN_FILE(__GTK_TEXT_BUFFER_RICH_TEXT_C__)
+gtk_text_buffer_deserialize
+gtk_text_buffer_deserialize_get_can_create_tags
+gtk_text_buffer_deserialize_set_can_create_tags
+gtk_text_buffer_get_deserialize_formats
+gtk_text_buffer_get_serialize_formats
+gtk_text_buffer_register_deserialize_format
+gtk_text_buffer_register_deserialize_tagset
+gtk_text_buffer_register_serialize_format
+gtk_text_buffer_register_serialize_tagset
+gtk_text_buffer_serialize
+gtk_text_buffer_unregister_deserialize_format
+gtk_text_buffer_unregister_serialize_format
+#endif
+#endif
+
 #if IN_HEADER(__GTK_RULER_H__)
 #if IN_FILE(__GTK_RULER_C__)
 gtk_ruler_draw_pos
@@ -2678,9 +2698,11 @@ gtk_selection_data_set_pixbuf
 gtk_selection_data_set_text
 gtk_selection_data_set_uris
 gtk_selection_data_targets_include_image
+gtk_selection_data_targets_include_rich_text
 gtk_selection_data_targets_include_text
 gtk_selection_data_targets_include_uri
 gtk_targets_include_image
+gtk_targets_include_rich_text
 gtk_targets_include_text
 gtk_targets_include_uri
 gtk_selection_owner_set
@@ -2688,14 +2710,18 @@ gtk_selection_owner_set_for_display
 gtk_selection_remove_all
 gtk_target_list_add
 gtk_target_list_add_image_targets
+gtk_target_list_add_rich_text_targets
 gtk_target_list_add_table
 gtk_target_list_add_text_targets
 gtk_target_list_add_uri_targets
 gtk_target_list_find
+gtk_target_list_get_type G_GNUC_CONST
 gtk_target_list_new
 gtk_target_list_ref
 gtk_target_list_remove
 gtk_target_list_unref
+gtk_target_table_new_from_list
+gtk_target_table_free
 #endif
 #endif
 
@@ -2934,6 +2960,7 @@ gtk_text_buffer_delete_selection
 gtk_text_buffer_end_user_action
 gtk_text_buffer_get_bounds
 gtk_text_buffer_get_char_count
+gtk_text_buffer_get_copy_target_list
 gtk_text_buffer_get_end_iter
 gtk_text_buffer_get_has_selection
 gtk_text_buffer_get_insert
@@ -2946,6 +2973,7 @@ gtk_text_buffer_get_iter_at_offset
 gtk_text_buffer_get_line_count
 gtk_text_buffer_get_mark
 gtk_text_buffer_get_modified
+gtk_text_buffer_get_paste_target_list
 gtk_text_buffer_get_selection_bound
 gtk_text_buffer_get_selection_bounds
 gtk_text_buffer_get_slice
index 25ae16664e9f18b3039cce43293e5917f9e34ef3..8eceb1dd8b1f4a9fbdb62e372c84cd02d011ea74 100644 (file)
@@ -27,6 +27,7 @@
 #include "gtkinvisible.h"
 #include "gtkmain.h"
 #include "gtkmarshalers.h"
+#include "gtktextbufferrichtext.h"
 #include "gtkintl.h"
 #include "gtkalias.h"
 
@@ -47,6 +48,7 @@ typedef struct _GtkClipboardClass GtkClipboardClass;
 
 typedef struct _RequestContentsInfo RequestContentsInfo;
 typedef struct _RequestTextInfo RequestTextInfo;
+typedef struct _RequestRichTextInfo RequestRichTextInfo;
 typedef struct _RequestImageInfo RequestImageInfo;
 typedef struct _RequestTargetsInfo RequestTargetsInfo;
 
@@ -97,6 +99,15 @@ struct _RequestTextInfo
   gpointer user_data;
 };
 
+struct _RequestRichTextInfo
+{
+  GtkClipboardRichTextReceivedFunc callback;
+  GdkAtom *atoms;
+  gint     n_atoms;
+  gint     current_atom;
+  gpointer user_data;
+};
+
 struct _RequestImageInfo
 {
   GtkClipboardImageReceivedFunc callback;
@@ -927,7 +938,7 @@ request_text_received_func (GtkClipboard     *clipboard,
   RequestTextInfo *info = data;
   gchar *result = NULL;
 
-  result = gtk_selection_data_get_text (selection_data);
+  result = (gchar *) gtk_selection_data_get_text (selection_data);
 
   if (!result)
     {
@@ -992,6 +1003,80 @@ gtk_clipboard_request_text (GtkClipboard                *clipboard,
                                  info);
 }
 
+static void
+request_rich_text_received_func (GtkClipboard     *clipboard,
+                                 GtkSelectionData *selection_data,
+                                 gpointer          data)
+{
+  RequestRichTextInfo *info = data;
+  guint8 *result = NULL;
+  gsize length = 0;
+
+  result = selection_data->data;
+  length = selection_data->length;
+
+  info->current_atom++;
+
+  if ((!result || length < 1) && (info->current_atom < info->n_atoms))
+    {
+      gtk_clipboard_request_contents (clipboard, info->atoms[info->current_atom],
+                                      request_rich_text_received_func,
+                                      info);
+      return;
+    }
+
+  info->callback (clipboard, selection_data->target, result, length,
+                  info->user_data);
+  g_free (info->atoms);
+  g_free (info);
+}
+
+/**
+ * gtk_clipboard_request_rich_text:
+ * @clipboard: a #GtkClipboard
+ * @buffer:    a #GtkTextBuffer
+ * @callback:  a function to call when the text is received,
+ *             or the retrieval fails. (It will always be called
+ *             one way or the other.)
+ * @user_data: user data to pass to @callback.
+ *
+ * Requests the contents of the clipboard as rich text. When the rich
+ * text is later received, @callback will be called.
+ *
+ * The @text parameter to @callback will contain the resulting rich
+ * text if the request succeeded, or %NULL if it failed. The @length
+ * parameter will contain @text's length. This function can fail for
+ * various reasons, in particular if the clipboard was empty or if the
+ * contents of the clipboard could not be converted into rich text form.
+ *
+ * Since: 2.10
+ **/
+void
+gtk_clipboard_request_rich_text (GtkClipboard                    *clipboard,
+                                 GtkTextBuffer                   *buffer,
+                                 GtkClipboardRichTextReceivedFunc callback,
+                                 gpointer                         user_data)
+{
+  RequestRichTextInfo *info;
+
+  g_return_if_fail (clipboard != NULL);
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+  g_return_if_fail (callback != NULL);
+
+  info = g_new (RequestRichTextInfo, 1);
+  info->callback = callback;
+  info->atoms = NULL;
+  info->n_atoms = 0;
+  info->current_atom = 0;
+  info->user_data = user_data;
+
+  info->atoms = gtk_text_buffer_get_deserialize_formats (buffer, &info->n_atoms);
+
+  gtk_clipboard_request_contents (clipboard, info->atoms[info->current_atom],
+                                 request_rich_text_received_func,
+                                 info);
+}
+
 static void 
 request_image_received_func (GtkClipboard     *clipboard,
                             GtkSelectionData *selection_data,
@@ -1143,6 +1228,8 @@ typedef struct
 {
   GMainLoop *loop;
   gpointer data;
+  GdkAtom format; /* used by rich text */
+  gsize length; /* used by rich text */
 } WaitResults;
 
 static void 
@@ -1254,6 +1341,75 @@ gtk_clipboard_wait_for_text (GtkClipboard *clipboard)
   return results.data;
 }
 
+static void
+clipboard_rich_text_received_func (GtkClipboard *clipboard,
+                                   GdkAtom       format,
+                                   const guint8 *text,
+                                   gsize         length,
+                                   gpointer      data)
+{
+  WaitResults *results = data;
+
+  results->data = g_memdup (text, length);
+  results->format = format;
+  results->length = length;
+  g_main_loop_quit (results->loop);
+}
+
+/**
+ * gtk_clipboard_wait_for_rich_text:
+ * @clipboard: a #GtkClipboard
+ * @buffer: a #GtkTextBuffer
+ * @length: return location for the length of the returned data
+ *
+ * Requests the contents of the clipboard as rich text.  This function
+ * waits for the data to be received using the main loop, so events,
+ * timeouts, etc, may be dispatched during the wait.
+ *
+ * Return value: a newly-allocated binary block of data which must
+ *               be freed with g_free(), or %NULL if retrieving
+ *               the selection data failed. (This could happen
+ *               for various reasons, in particular if the
+ *               clipboard was empty or if the contents of the
+ *               clipboard could not be converted into text form.)
+ *
+ * Since: 2.10
+ **/
+guint8 *
+gtk_clipboard_wait_for_rich_text (GtkClipboard  *clipboard,
+                                  GtkTextBuffer *buffer,
+                                  GdkAtom       *format,
+                                  gsize         *length)
+{
+  WaitResults results;
+
+  g_return_val_if_fail (clipboard != NULL, NULL);
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
+  g_return_val_if_fail (format != NULL, NULL);
+  g_return_val_if_fail (length != NULL, NULL);
+
+  results.data = NULL;
+  results.loop = g_main_loop_new (NULL, TRUE);
+
+  gtk_clipboard_request_rich_text (clipboard, buffer,
+                                   clipboard_rich_text_received_func,
+                                   &results);
+
+  if (g_main_loop_is_running (results.loop))
+    {
+      GDK_THREADS_LEAVE ();
+      g_main_loop_run (results.loop);
+      GDK_THREADS_ENTER ();
+    }
+
+  g_main_loop_unref (results.loop);
+
+  *format = results.format;
+  *length = results.length;
+
+  return results.data;
+}
+
 static void 
 clipboard_image_received_func (GtkClipboard *clipboard,
                               GdkPixbuf    *pixbuf,
@@ -1361,6 +1517,45 @@ gtk_clipboard_wait_is_text_available (GtkClipboard *clipboard)
   return result;
 }
 
+/**
+ * gtk_clipboard_wait_is_rich_text_available:
+ * @clipboard: a #GtkClipboard
+ * @buffer: a #GtkTextBuffer
+ *
+ * Test to see if there is rich text available to be pasted
+ * This is done by requesting the TARGETS atom and checking
+ * if it contains any of the supported rich text targets. This function
+ * waits for the data to be received using the main loop, so events,
+ * timeouts, etc, may be dispatched during the wait.
+ *
+ * This function is a little faster than calling
+ * gtk_clipboard_wait_for_rich_text() since it doesn't need to retrieve
+ * the actual text.
+ *
+ * Return value: %TRUE is there is rich text available, %FALSE otherwise.
+ *
+ * Since: 2.10
+ **/
+gboolean
+gtk_clipboard_wait_is_rich_text_available (GtkClipboard  *clipboard,
+                                           GtkTextBuffer *buffer)
+{
+  GtkSelectionData *data;
+  gboolean result = FALSE;
+
+  g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE);
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE);
+
+  data = gtk_clipboard_wait_for_contents (clipboard, gdk_atom_intern_static_string ("TARGETS"));
+  if (data)
+    {
+      result = gtk_selection_data_targets_include_rich_text (data, buffer);
+      gtk_selection_data_free (data);
+    }
+
+  return result;
+}
+
 /**
  * gtk_clipboard_wait_is_image_available:
  * @clipboard: a #GtkClipboard
index 9be35a63602b16494bf2e5bf1c0a27f5050b6b85..46e9ea6b36587cbe4d986d0b2715cb5dd7bf79ea 100644 (file)
@@ -30,19 +30,24 @@ G_BEGIN_DECLS
 #define GTK_CLIPBOARD(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CLIPBOARD, GtkClipboard))
 #define GTK_IS_CLIPBOARD(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_CLIPBOARD))
 
-typedef void (* GtkClipboardReceivedFunc)        (GtkClipboard     *clipboard,
-                                                 GtkSelectionData *selection_data,
-                                                 gpointer          data);
-typedef void (* GtkClipboardTextReceivedFunc)    (GtkClipboard     *clipboard,
-                                                 const gchar      *text,
-                                                 gpointer          data);
-typedef void (* GtkClipboardImageReceivedFunc)   (GtkClipboard     *clipboard,
-                                                 GdkPixbuf        *pixbuf,
-                                                 gpointer          data);
-typedef void (* GtkClipboardTargetsReceivedFunc) (GtkClipboard     *clipboard,
-                                                 GdkAtom          *atoms,
-                                                 gint              n_atoms,
-                                                 gpointer          data);
+typedef void (* GtkClipboardReceivedFunc)         (GtkClipboard     *clipboard,
+                                                  GtkSelectionData *selection_data,
+                                                  gpointer          data);
+typedef void (* GtkClipboardTextReceivedFunc)     (GtkClipboard     *clipboard,
+                                                  const gchar      *text,
+                                                  gpointer          data);
+typedef void (* GtkClipboardRichTextReceivedFunc) (GtkClipboard     *clipboard,
+                                                   GdkAtom           format,
+                                                  const guint8     *text,
+                                                   gsize             length,
+                                                  gpointer          data);
+typedef void (* GtkClipboardImageReceivedFunc)    (GtkClipboard     *clipboard,
+                                                  GdkPixbuf        *pixbuf,
+                                                  gpointer          data);
+typedef void (* GtkClipboardTargetsReceivedFunc)  (GtkClipboard     *clipboard,
+                                                  GdkAtom          *atoms,
+                                                  gint              n_atoms,
+                                                  gpointer          data);
 
 /* Should these functions have GtkClipboard *clipboard as the first argument?
  * right now for ClearFunc, you may have trouble determining _which_ clipboard
@@ -86,32 +91,42 @@ void     gtk_clipboard_set_text       (GtkClipboard          *clipboard,
 void     gtk_clipboard_set_image      (GtkClipboard          *clipboard,
                                       GdkPixbuf             *pixbuf);
 
-void gtk_clipboard_request_contents (GtkClipboard                    *clipboard,
-                                    GdkAtom                          target,
-                                    GtkClipboardReceivedFunc         callback,
-                                    gpointer                         user_data);
-void gtk_clipboard_request_text     (GtkClipboard                    *clipboard,
-                                    GtkClipboardTextReceivedFunc     callback,
-                                    gpointer                         user_data);
-void gtk_clipboard_request_image    (GtkClipboard                    *clipboard,
-                                    GtkClipboardImageReceivedFunc    callback,
-                                    gpointer                         user_data);
-void gtk_clipboard_request_targets  (GtkClipboard                    *clipboard,
-                                    GtkClipboardTargetsReceivedFunc  callback,
-                                    gpointer                         user_data);
-
-GtkSelectionData *gtk_clipboard_wait_for_contents (GtkClipboard  *clipboard,
-                                                  GdkAtom        target);
-gchar *           gtk_clipboard_wait_for_text     (GtkClipboard  *clipboard);
-GdkPixbuf *       gtk_clipboard_wait_for_image    (GtkClipboard  *clipboard);
-gboolean          gtk_clipboard_wait_for_targets  (GtkClipboard  *clipboard,
-                                                  GdkAtom      **targets,
-                                                  gint          *n_targets);
-
-gboolean gtk_clipboard_wait_is_text_available   (GtkClipboard *clipboard);
-gboolean gtk_clipboard_wait_is_image_available  (GtkClipboard *clipboard);
-gboolean gtk_clipboard_wait_is_target_available (GtkClipboard *clipboard,
-                                                GdkAtom       target);
+void gtk_clipboard_request_contents  (GtkClipboard                     *clipboard,
+                                      GdkAtom                           target,
+                                      GtkClipboardReceivedFunc          callback,
+                                      gpointer                          user_data);
+void gtk_clipboard_request_text      (GtkClipboard                     *clipboard,
+                                      GtkClipboardTextReceivedFunc      callback,
+                                      gpointer                          user_data);
+void gtk_clipboard_request_rich_text (GtkClipboard                     *clipboard,
+                                      GtkTextBuffer                    *buffer,
+                                      GtkClipboardRichTextReceivedFunc  callback,
+                                      gpointer                          user_data);
+void gtk_clipboard_request_image     (GtkClipboard                     *clipboard,
+                                      GtkClipboardImageReceivedFunc     callback,
+                                      gpointer                          user_data);
+void gtk_clipboard_request_targets   (GtkClipboard                     *clipboard,
+                                      GtkClipboardTargetsReceivedFunc   callback,
+                                      gpointer                          user_data);
+
+GtkSelectionData *gtk_clipboard_wait_for_contents  (GtkClipboard  *clipboard,
+                                                    GdkAtom        target);
+gchar *           gtk_clipboard_wait_for_text      (GtkClipboard  *clipboard);
+guint8 *          gtk_clipboard_wait_for_rich_text (GtkClipboard  *clipboard,
+                                                    GtkTextBuffer *buffer,
+                                                    GdkAtom       *format,
+                                                    gsize         *size);
+GdkPixbuf *       gtk_clipboard_wait_for_image     (GtkClipboard  *clipboard);
+gboolean          gtk_clipboard_wait_for_targets   (GtkClipboard  *clipboard,
+                                                    GdkAtom      **targets,
+                                                    gint          *n_targets);
+
+gboolean gtk_clipboard_wait_is_text_available      (GtkClipboard  *clipboard);
+gboolean gtk_clipboard_wait_is_rich_text_available (GtkClipboard  *clipboard,
+                                                    GtkTextBuffer *buffer);
+gboolean gtk_clipboard_wait_is_image_available     (GtkClipboard  *clipboard);
+gboolean gtk_clipboard_wait_is_target_available    (GtkClipboard  *clipboard,
+                                                    GdkAtom        target);
 
 
 void gtk_clipboard_set_can_store (GtkClipboard         *clipboard,
index 6272f5c46b21dc04e7c2c1cd23496dff0531225b..9670353498055ceca32b4211df844d09d493ff48 100644 (file)
@@ -58,6 +58,7 @@
 
 #include "gtkmain.h"
 #include "gtkselection.h"
+#include "gtktextbufferrichtext.h"
 #include "gtkintl.h"
 #include "gdk-pixbuf/gdk-pixbuf.h"
 
@@ -213,13 +214,16 @@ gtk_target_list_new (const GtkTargetEntry *targets,
  * 
  * Increases the reference count of a #GtkTargetList by one.
  *
+ * Return value: the passed in #GtkTargetList.
  **/
-void               
+GtkTargetList *
 gtk_target_list_ref (GtkTargetList *list)
 {
-  g_return_if_fail (list != NULL);
+  g_return_val_if_fail (list != NULL, NULL);
 
   list->ref_count++;
+
+  return list;
 }
 
 /**
@@ -338,6 +342,45 @@ gtk_target_list_add_text_targets (GtkTargetList *list,
   gtk_target_list_add (list, text_plain_atom, 0, info);  
 }
 
+/**
+ * gtk_target_list_add_rich_text_targets:
+ * @list: a #GtkTargetList
+ * @info: an ID that will be passed back to the application
+ * @deserializable: if %TRUE, then deserializable rich text formats
+ *                  will be added, serializable formats otherwise.
+ * @buffer: a #GtkTextBuffer.
+ *
+ * Appends the rich text targets registered with
+ * gtk_text_buffer_register_serialize_format() or
+ * gtk_text_buffer_register_deserialize_format() to the target list. All
+ * targets are added with the same @info.
+ *
+ * Since: 2.10
+ **/
+void
+gtk_target_list_add_rich_text_targets (GtkTargetList  *list,
+                                       guint           info,
+                                       gboolean        deserializable,
+                                       GtkTextBuffer  *buffer)
+{
+  GdkAtom *atoms;
+  gint     n_atoms;
+  gint     i;
+
+  g_return_if_fail (list != NULL);
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+
+  if (deserializable)
+    atoms = gtk_text_buffer_get_deserialize_formats (buffer, &n_atoms);
+  else
+    atoms = gtk_text_buffer_get_serialize_formats (buffer, &n_atoms);
+
+  for (i = 0; i < n_atoms; i++)
+    gtk_target_list_add (list, atoms[i], 0, info);
+
+  g_free (atoms);
+}
+
 /**
  * gtk_target_list_add_image_targets:
  * @list: a #GtkTargetList
@@ -514,6 +557,72 @@ gtk_target_list_find (GtkTargetList *list,
   return FALSE;
 }
 
+/**
+ * gtk_target_table_new_from_list:
+ * @list: a #GtkTargetList
+ * @n_targets: return location for the number ot targets in the table
+ *
+ * This function creates an #GtkTargetEntry array that contains the
+ * same targets as the passed %list. The returned table is newly
+ * allocated and should be freed using gtk_target_table_free() when no
+ * longer needed.
+ *
+ * Return value: the new table.
+ *
+ * Since: 2.10
+ **/
+GtkTargetEntry *
+gtk_target_table_new_from_list (GtkTargetList *list,
+                                gint          *n_targets)
+{
+  GtkTargetEntry *targets;
+  GList          *tmp_list;
+  gint            i;
+
+  g_return_val_if_fail (list != NULL, NULL);
+  g_return_val_if_fail (n_targets != NULL, NULL);
+
+  *n_targets = g_list_length (list->list);
+  targets = g_new0 (GtkTargetEntry, *n_targets);
+
+  for (i = 0, tmp_list = list->list;
+       i < *n_targets;
+       i++, tmp_list = g_list_next (tmp_list))
+    {
+      GtkTargetPair *pair = tmp_list->data;
+
+      targets[i].target = gdk_atom_name (pair->target);
+      targets[i].flags  = pair->flags;
+      targets[i].info   = pair->info;
+    }
+
+  return targets;
+}
+
+/**
+ * gtk_target_table_free:
+ * @targets: a #GtkTargetEntry array
+ * @n_targets: the number of entries in the array
+ *
+ * This function frees a target table as returned by
+ * gtk_target_table_new_from_list()
+ *
+ * Since: 2.10
+ **/
+void
+gtk_target_table_free (GtkTargetEntry *targets,
+                       gint            n_targets)
+{
+  gint i;
+
+  g_return_if_fail (targets == NULL || n_targets > 0);
+
+  for (i = 0; i < n_targets; i++)
+    g_free (targets[i].target);
+
+  g_free (targets);
+}
+
 /**
  * gtk_selection_owner_set_for_display:
  * @display: the #Gdkdisplay where the selection is set 
@@ -1608,7 +1717,54 @@ gtk_targets_include_text (GdkAtom *targets,
   
   return result;
 }
-                                   
+
+/**
+ * gtk_targets_include_rich_text:
+ * @targets: an array of #GdkAtom<!-- -->s
+ * @n_targets: the length of @targets
+ * @buffer: a #GtkTextBuffer
+ *
+ * Determines if any of the targets in @targets can be used to
+ * provide rich text.
+ *
+ * Return value: %TRUE if @targets include a suitable target for rich text,
+ *               otherwise %FALSE.
+ *
+ * Since: 2.10
+ **/
+gboolean
+gtk_targets_include_rich_text (GdkAtom       *targets,
+                               gint           n_targets,
+                               GtkTextBuffer *buffer)
+{
+  GdkAtom *rich_targets;
+  gint n_rich_targets;
+  gint i, j;
+  gboolean result = FALSE;
+
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE);
+
+  rich_targets = gtk_text_buffer_get_deserialize_formats (buffer,
+                                                          &n_rich_targets);
+
+  for (i = 0; i < n_targets; i++)
+    {
+      for (j = 0; j < n_rich_targets; j++)
+        {
+          if (targets[i] == rich_targets[j])
+            {
+              result = TRUE;
+              goto done;
+            }
+        }
+    }
+
+ done:
+  g_free (rich_targets);
+
+  return result;
+}
+
 /**
  * gtk_selection_data_targets_include_text:
  * @selection_data: a #GtkSelectionData object
@@ -1638,6 +1794,42 @@ gtk_selection_data_targets_include_text (GtkSelectionData *selection_data)
   return result;
 }
 
+/**
+ * gtk_selection_data_targets_include_rich_text:
+ * @selection_data: a #GtkSelectionData object
+ * @buffer: a #GtkTextBuffer
+ *
+ * Given a #GtkSelectionData object holding a list of targets,
+ * determines if any of the targets in @targets can be used to
+ * provide rich text.
+ *
+ * Return value: %TRUE if @selection_data holds a list of targets,
+ *               and a suitable target for rich text is included,
+ *               otherwise %FALSE.
+ *
+ * Since: 2.10
+ **/
+gboolean
+gtk_selection_data_targets_include_rich_text (GtkSelectionData *selection_data,
+                                              GtkTextBuffer    *buffer)
+{
+  GdkAtom *targets;
+  gint n_targets;
+  gboolean result = FALSE;
+
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE);
+
+  init_atoms ();
+
+  if (gtk_selection_data_get_targets (selection_data, &targets, &n_targets))
+    {
+      result = gtk_targets_include_rich_text (targets, n_targets, buffer);
+      g_free (targets);
+    }
+
+  return result;
+}
+
 /**
  * gtk_targets_include_image:
  * @targets: an array of #GdkAtom<!-- -->s
@@ -2756,6 +2948,19 @@ gtk_selection_data_get_type (void)
   return our_type;
 }
 
+GType
+gtk_target_list_get_type (void)
+{
+  static GType our_type = 0;
+
+  if (our_type == 0)
+    our_type = g_boxed_type_register_static (I_("GtkTargetList"),
+                                            (GBoxedCopyFunc) gtk_target_list_ref,
+                                            (GBoxedFreeFunc) gtk_target_list_unref);
+
+  return our_type;
+}
+
 static int 
 gtk_selection_bytes_per_item (gint format)
 {
index 038a16a97217a75c5644cc125fbd0e753df1d4fe..9e97ae2a6dd8185606f9da83950b91b7ca921786 100644 (file)
@@ -31,6 +31,7 @@
 #include <gdk/gdk.h>
 #include <gtk/gtkenums.h>
 #include <gtk/gtkwidget.h>
+#include <gtk/gtktextiter.h>
 
 G_BEGIN_DECLS
 
@@ -38,6 +39,7 @@ typedef struct _GtkTargetList    GtkTargetList;
 typedef struct _GtkTargetEntry   GtkTargetEntry;
 
 #define GTK_TYPE_SELECTION_DATA (gtk_selection_data_get_type ())
+#define GTK_TYPE_TARGET_LIST    (gtk_target_list_get_type ())
 
 /* The contents of a selection are returned in a GtkSelectionData
  * structure. selection/target identify the request.  type specifies
@@ -87,19 +89,23 @@ struct _GtkTargetPair {
 
 GtkTargetList *gtk_target_list_new       (const GtkTargetEntry *targets,
                                          guint                 ntargets);
-void           gtk_target_list_ref       (GtkTargetList  *list);
+GtkTargetList *gtk_target_list_ref       (GtkTargetList  *list);
 void           gtk_target_list_unref     (GtkTargetList  *list);
 void           gtk_target_list_add       (GtkTargetList  *list,
                                          GdkAtom         target,
                                          guint           flags,
                                          guint           info);
-void           gtk_target_list_add_text_targets  (GtkTargetList  *list,
-                                                 guint           info);
-void           gtk_target_list_add_image_targets (GtkTargetList  *list,
-                                                 guint           info,
-                                                 gboolean        writable);
-void           gtk_target_list_add_uri_targets   (GtkTargetList  *list,
-                                                 guint           info);
+void           gtk_target_list_add_text_targets      (GtkTargetList  *list,
+                                                      guint           info);
+void           gtk_target_list_add_rich_text_targets (GtkTargetList  *list,
+                                                      guint           info,
+                                                      gboolean        deserializable,
+                                                      GtkTextBuffer  *buffer);
+void           gtk_target_list_add_image_targets     (GtkTargetList  *list,
+                                                      guint           info,
+                                                      gboolean        writable);
+void           gtk_target_list_add_uri_targets       (GtkTargetList  *list,
+                                                      guint           info);
 void           gtk_target_list_add_table (GtkTargetList        *list,
                                          const GtkTargetEntry *targets,
                                          guint                 ntargets);
@@ -109,6 +115,11 @@ gboolean       gtk_target_list_find      (GtkTargetList  *list,
                                          GdkAtom         target,
                                          guint          *info);
 
+GtkTargetEntry * gtk_target_table_new_from_list (GtkTargetList  *list,
+                                                 gint           *n_targets);
+void             gtk_target_table_free          (GtkTargetEntry *targets,
+                                                 gint            n_targets);
+
 /* Public interface */
 
 gboolean gtk_selection_owner_set             (GtkWidget  *widget,
@@ -153,16 +164,21 @@ gboolean gtk_selection_data_get_targets          (GtkSelectionData  *selection_d
                                                  GdkAtom          **targets,
                                                  gint              *n_atoms);
 gboolean gtk_selection_data_targets_include_text (GtkSelectionData  *selection_data);
+gboolean gtk_selection_data_targets_include_rich_text (GtkSelectionData *selection_data,
+                                                       GtkTextBuffer    *buffer);
 gboolean gtk_selection_data_targets_include_image (GtkSelectionData  *selection_data,
                                                   gboolean           writable);
 gboolean gtk_selection_data_targets_include_uri  (GtkSelectionData  *selection_data);
-gboolean gtk_targets_include_text                (GdkAtom *targets,
-                                                 gint     n_targets);
-gboolean gtk_targets_include_image               (GdkAtom *targets,
-                                                 gint     n_targets,
-                                                 gboolean writable);
-gboolean gtk_targets_include_uri                 (GdkAtom *targets,
-                                                 gint     n_targets);
+gboolean gtk_targets_include_text                (GdkAtom       *targets,
+                                                 gint           n_targets);
+gboolean gtk_targets_include_rich_text           (GdkAtom       *targets,
+                                                 gint           n_targets,
+                                                  GtkTextBuffer *buffer);
+gboolean gtk_targets_include_image               (GdkAtom       *targets,
+                                                 gint           n_targets,
+                                                 gboolean       writable);
+gboolean gtk_targets_include_uri                 (GdkAtom       *targets,
+                                                 gint           n_targets);
 
 /* Called when a widget is destroyed */
 
@@ -186,6 +202,7 @@ GType             gtk_selection_data_get_type (void) G_GNUC_CONST;
 GtkSelectionData *gtk_selection_data_copy     (GtkSelectionData *data);
 void             gtk_selection_data_free     (GtkSelectionData *data);
 
+GType             gtk_target_list_get_type    (void) G_GNUC_CONST;
 
 G_END_DECLS
 
index e65c71bf3afa93b63d49dcc9833918cd0fd03c54..b6d2788c097e7ffd94bce2217e32206745a1479d 100644 (file)
@@ -1,5 +1,6 @@
 /* GTK - The GIMP Toolkit
  * gtktextbuffer.c Copyright (C) 2000 Red Hat, Inc.
+ *                 Copyright (C) 2004 Nokia Corporation
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
  */
 
-
 #include <config.h>
 #include <string.h>
 #include <stdarg.h>
 
-
 #define GTK_TEXT_USE_INTERNAL_UNSUPPORTED_API
 #include "gtkclipboard.h"
+#include "gtkdnd.h"
 #include "gtkinvisible.h"
 #include "gtkmarshalers.h"
 #include "gtktextbuffer.h"
+#include "gtktextbufferrichtext.h"
 #include "gtktextbtree.h"
 #include "gtktextiterprivate.h"
 #include "gtkprivate.h"
 #include "gtkalias.h"
 
 
+#define GTK_TEXT_BUFFER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_TEXT_BUFFER, GtkTextBufferPrivate))
+
+typedef struct _GtkTextBufferPrivate GtkTextBufferPrivate;
+
+struct _GtkTextBufferPrivate
+{
+  GtkTargetList  *copy_target_list;
+  GtkTargetEntry *copy_target_entries;
+  gint            n_copy_target_entries;
+
+  GtkTargetList  *paste_target_list;
+  GtkTargetEntry *paste_target_entries;
+  gint            n_paste_target_entries;
+};
+
+
 typedef struct _ClipboardRequest ClipboardRequest;
 
 struct _ClipboardRequest
@@ -74,25 +91,18 @@ enum {
 
   /* Construct */
   PROP_TAG_TABLE,
-  
+
   /* Normal */
   PROP_TEXT,
-  PROP_HAS_SELECTION
-};
-
-enum {
-  TARGET_STRING,
-  TARGET_TEXT,
-  TARGET_COMPOUND_TEXT,
-  TARGET_UTF8_STRING,
-  TARGET_TEXT_BUFFER_CONTENTS
+  PROP_HAS_SELECTION,
+  PROP_COPY_TARGET_LIST,
+  PROP_PASTE_TARGET_LIST
 };
 
 static void gtk_text_buffer_init       (GtkTextBuffer      *tkxt_buffer);
 static void gtk_text_buffer_class_init (GtkTextBufferClass *klass);
 static void gtk_text_buffer_finalize   (GObject            *object);
 
-
 static void gtk_text_buffer_real_insert_text           (GtkTextBuffer     *buffer,
                                                         GtkTextIter       *iter,
                                                         const gchar       *text,
@@ -127,6 +137,8 @@ static void update_selection_clipboards           (GtkTextBuffer *buffer);
 
 static GtkTextBuffer *create_clipboard_contents_buffer (GtkTextBuffer *buffer);
 
+static void gtk_text_buffer_free_target_lists     (GtkTextBuffer *buffer);
+
 static GObjectClass *parent_class = NULL;
 static guint signals[LAST_SIGNAL] = { 0 };
 
@@ -138,6 +150,8 @@ static void gtk_text_buffer_get_property (GObject         *object,
                                          guint            prop_id,
                                          GValue          *value,
                                          GParamSpec      *pspec);
+static void gtk_text_buffer_notify       (GObject         *object,
+                                          GParamSpec      *pspec);
 
 
 GType
@@ -177,6 +191,7 @@ gtk_text_buffer_class_init (GtkTextBufferClass *klass)
   object_class->finalize = gtk_text_buffer_finalize;
   object_class->set_property = gtk_text_buffer_set_property;
   object_class->get_property = gtk_text_buffer_get_property;
+  object_class->notify       = gtk_text_buffer_notify;
  
   klass->insert_text = gtk_text_buffer_real_insert_text;
   klass->insert_pixbuf = gtk_text_buffer_real_insert_pixbuf;
@@ -227,7 +242,39 @@ gtk_text_buffer_class_init (GtkTextBufferClass *klass)
                                                          P_("Has selection"),
                                                          P_("Whether the buffer has some text currently selected"),
                                                          FALSE,
-                                                         G_PARAM_READABLE));
+                                                         GTK_PARAM_READABLE));
+
+  /**
+   * GtkTextBuffer:copy-target-list:
+   *
+   * The list of targets this buffer supports for clipboard copying
+   * and as DND source.
+   *
+   * Since: 2.10
+   */
+  g_object_class_install_property (object_class,
+                                   PROP_COPY_TARGET_LIST,
+                                   g_param_spec_boxed ("copy-target-list",
+                                                       P_("Copy target list"),
+                                                       P_("The list of targets this buffer supports for clipboard copying and DND source"),
+                                                       GTK_TYPE_TARGET_LIST,
+                                                       GTK_PARAM_READABLE));
+
+  /**
+   * GtkTextBuffer:paste-target-list:
+   *
+   * The list of targets this buffer supports for clipboard pasting
+   * and as DND destination.
+   *
+   * Since: 2.10
+   */
+  g_object_class_install_property (object_class,
+                                   PROP_PASTE_TARGET_LIST,
+                                   g_param_spec_boxed ("paste-target-list",
+                                                       P_("Paste target list"),
+                                                       P_("The list of targets this buffer supports for clipboard pasting and DND destination"),
+                                                       GTK_TYPE_TARGET_LIST,
+                                                       GTK_PARAM_READABLE));
 
   signals[INSERT_TEXT] =
     g_signal_new (I_("insert_text"),
@@ -378,7 +425,9 @@ gtk_text_buffer_class_init (GtkTextBufferClass *klass)
                   NULL, NULL,
                   _gtk_marshal_VOID__VOID,
                   G_TYPE_NONE,
-                  0);  
+                  0);
+
+  g_type_class_add_private (object_class, sizeof (GtkTextBufferPrivate));
 }
 
 static void
@@ -386,6 +435,9 @@ gtk_text_buffer_init (GtkTextBuffer *buffer)
 {
   buffer->clipboard_contents_buffers = NULL;
   buffer->tag_table = NULL;
+
+  /* allow copying of arbiatray stuff in the internal rich text format */
+  gtk_text_buffer_register_serialize_tagset (buffer, NULL);
 }
 
 static void
@@ -428,9 +480,9 @@ gtk_text_buffer_set_property (GObject         *object,
     case PROP_TAG_TABLE:
       set_table (text_buffer, g_value_get_object (value));
       break;
-      
+
     case PROP_TEXT:
-      gtk_text_buffer_set_text (text_buffer, 
+      gtk_text_buffer_set_text (text_buffer,
                                g_value_get_string (value), -1);
       break;
 
@@ -454,28 +506,48 @@ gtk_text_buffer_get_property (GObject         *object,
     case PROP_TAG_TABLE:
       g_value_set_object (value, get_table (text_buffer));
       break;
-      
+
     case PROP_TEXT:
-    {
-      GtkTextIter start, end;
-      
-      gtk_text_buffer_get_start_iter (text_buffer, &start);
-      gtk_text_buffer_get_end_iter (text_buffer, &end);
-           
-      g_value_set_string (value,
-                         gtk_text_buffer_get_text (text_buffer, &start, &end, FALSE));
-      break;
-    }
+      {
+        GtkTextIter start, end;
+
+        gtk_text_buffer_get_start_iter (text_buffer, &start);
+        gtk_text_buffer_get_end_iter (text_buffer, &end);
+
+        g_value_set_string (value,
+                            gtk_text_buffer_get_text (text_buffer,
+                                                      &start, &end, FALSE));
+        break;
+      }
 
     case PROP_HAS_SELECTION:
       g_value_set_boolean (value, text_buffer->has_selection);
       break;
 
+    case PROP_COPY_TARGET_LIST:
+      g_value_set_boxed (value, gtk_text_buffer_get_copy_target_list (text_buffer));
+      break;
+
+    case PROP_PASTE_TARGET_LIST:
+      g_value_set_boxed (value, gtk_text_buffer_get_paste_target_list (text_buffer));
+      break;
+
     default:
       break;
     }
 }
 
+static void
+gtk_text_buffer_notify (GObject    *object,
+                        GParamSpec *pspec)
+{
+  if (!strcmp (pspec->name, "copy-target-list") ||
+      !strcmp (pspec->name, "paste-target-list"))
+    {
+      gtk_text_buffer_free_target_lists (GTK_TEXT_BUFFER (object));
+    }
+}
+
 /**
  * gtk_text_buffer_new:
  * @table: a tag table, or NULL to create a new one
@@ -498,11 +570,14 @@ static void
 gtk_text_buffer_finalize (GObject *object)
 {
   GtkTextBuffer *buffer;
+  GtkTextBufferPrivate *priv;
 
   buffer = GTK_TEXT_BUFFER (object);
 
   remove_all_selection_clipboards (buffer);
 
+  priv = GTK_TEXT_BUFFER_GET_PRIVATE (buffer);
+
   if (buffer->tag_table)
     {
       _gtk_text_tag_table_remove_buffer (buffer->tag_table, buffer);
@@ -520,7 +595,9 @@ gtk_text_buffer_finalize (GObject *object)
     free_log_attr_cache (buffer->log_attr_cache);
 
   buffer->log_attr_cache = NULL;
-  
+
+  gtk_text_buffer_free_target_lists (buffer);
+
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
@@ -2840,22 +2917,36 @@ clipboard_get_selection_cb (GtkClipboard     *clipboard,
 
   if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
     {
-      if (selection_data->target ==
-          gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"))
+      if (info == GTK_TEXT_BUFFER_TARGET_INFO_BUFFER_CONTENTS)
         {
           /* Provide the address of the buffer; this will only be
            * used within-process
            */
           gtk_selection_data_set (selection_data,
-                                  gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"),
+                                  selection_data->target,
                                   8, /* bytes */
                                   (void*)&buffer,
                                   sizeof (buffer));
         }
+      else if (info == GTK_TEXT_BUFFER_TARGET_INFO_RICH_TEXT)
+        {
+          guint8 *str;
+          gsize   len;
+
+          str = gtk_text_buffer_serialize (buffer, buffer,
+                                           selection_data->target,
+                                           &start, &end, &len);
+
+          gtk_selection_data_set (selection_data,
+                                  selection_data->target,
+                                  8, /* bytes */
+                                  str, len);
+          g_free (str);
+        }
       else
         {
           gchar *str;
-          
+
           str = gtk_text_iter_get_visible_text (&start, &end);
           gtk_selection_data_set_text (selection_data, str, -1);
           g_free (str);
@@ -2870,8 +2961,11 @@ create_clipboard_contents_buffer (GtkTextBuffer *buffer)
 
   contents = gtk_text_buffer_new (gtk_text_buffer_get_tag_table (buffer));
 
-  g_object_set_data (G_OBJECT (contents), I_("gtk-text-buffer-clipboard"), GINT_TO_POINTER (1));
-  
+  g_object_set_data (G_OBJECT (contents), I_("gtk-text-buffer-clipboard-source"),
+                     buffer);
+  g_object_set_data (G_OBJECT (contents), I_("gtk-text-buffer-clipboard"),
+                     GINT_TO_POINTER (1));
+
   return contents;
 }
 
@@ -2882,31 +2976,50 @@ clipboard_get_contents_cb (GtkClipboard     *clipboard,
                            guint             info,
                            gpointer          data)
 {
-  GtkTextBuffer *contents;
+  GtkTextBuffer *contents = GTK_TEXT_BUFFER (data);
 
-  contents = GTK_TEXT_BUFFER (data);
-  
   g_assert (contents); /* This should never be called unless we own the clipboard */
 
-  if (selection_data->target ==
-      gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"))
+  if (info == GTK_TEXT_BUFFER_TARGET_INFO_BUFFER_CONTENTS)
     {
       /* Provide the address of the clipboard buffer; this will only
        * be used within-process. OK to supply a NULL value for contents.
        */
       gtk_selection_data_set (selection_data,
-                              gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"),
+                              selection_data->target,
                               8, /* bytes */
                               (void*)&contents,
                               sizeof (contents));
     }
+  else if (info == GTK_TEXT_BUFFER_TARGET_INFO_RICH_TEXT)
+    {
+      GtkTextBuffer *clipboard_source_buffer;
+      GtkTextIter start, end;
+      guint8 *str;
+      gsize   len;
+
+      clipboard_source_buffer = g_object_get_data (G_OBJECT (contents),
+                                                   "gtk-text-buffer-clipboard-source");
+
+      gtk_text_buffer_get_bounds (contents, &start, &end);
+
+      str = gtk_text_buffer_serialize (clipboard_source_buffer, contents,
+                                       selection_data->target,
+                                       &start, &end, &len);
+
+      gtk_selection_data_set (selection_data,
+                             selection_data->target,
+                             8, /* bytes */
+                             str, len);
+      g_free (str);
+    }
   else
     {
       gchar *str;
       GtkTextIter start, end;
-      
+
       gtk_text_buffer_get_bounds (contents, &start, &end);
-      
+
       str = gtk_text_iter_get_visible_text (&start, &end);
       gtk_selection_data_set_text (selection_data, str, -1);
       g_free (str);
@@ -3052,7 +3165,8 @@ selection_data_get_buffer (GtkSelectionData *selection_data,
   if (gdk_window_get_window_type (owner) == GDK_WINDOW_FOREIGN)
     return NULL;
  
-  if (selection_data->type != gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"))
+  if (selection_data->type !=
+      gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"))
     return NULL;
 
   if (selection_data->length != sizeof (src_buffer))
@@ -3099,6 +3213,62 @@ restore_iter (const GtkTextIter *iter,
 }
 #endif
 
+static void
+clipboard_rich_text_received (GtkClipboard *clipboard,
+                              GdkAtom       format,
+                              const guint8 *text,
+                              gsize         length,
+                              gpointer      data)
+{
+  ClipboardRequest *request_data = data;
+  GtkTextIter insert_point;
+  gboolean retval = TRUE;
+  GError *error = NULL;
+  GtkTextBufferPrivate *priv;
+
+  priv = GTK_TEXT_BUFFER_GET_PRIVATE (request_data->buffer);
+
+  if (text != NULL && length > 0)
+    {
+      pre_paste_prep (request_data, &insert_point);
+
+      if (request_data->interactive)
+        gtk_text_buffer_begin_user_action (request_data->buffer);
+
+      if (!request_data->interactive ||
+          gtk_text_iter_can_insert (&insert_point,
+                                    request_data->default_editable))
+        {
+          retval = gtk_text_buffer_deserialize (request_data->buffer,
+                                                request_data->buffer,
+                                                format,
+                                                &insert_point,
+                                                text, length,
+                                                &error);
+        }
+
+      if (!retval)
+        {
+          g_warning ("error pasting: %s\n", error->message);
+          g_clear_error (&error);
+        }
+
+      if (request_data->interactive)
+        gtk_text_buffer_end_user_action (request_data->buffer);
+
+      if (retval)
+        {
+          post_paste_cleanup (request_data);
+          return;
+        }
+    }
+
+  /* Request the text selection instead */
+  gtk_clipboard_request_text (clipboard,
+                              clipboard_text_received,
+                              data);
+}
+
 static void
 paste_from_buffer (ClipboardRequest    *request_data,
                    GtkTextBuffer       *src_buffer,
@@ -3145,7 +3315,8 @@ clipboard_clipboard_buffer_received (GtkClipboard     *clipboard,
 {
   ClipboardRequest *request_data = data;
   GtkTextBuffer *src_buffer;
-  
+  GtkTextBufferPrivate *priv;
+
   src_buffer = selection_data_get_buffer (selection_data, request_data); 
 
   if (src_buffer)
@@ -3155,7 +3326,7 @@ clipboard_clipboard_buffer_received (GtkClipboard     *clipboard,
       if (g_object_get_data (G_OBJECT (src_buffer), "gtk-text-buffer-clipboard"))
        {
          gtk_text_buffer_get_bounds (src_buffer, &start, &end);
-      
+
          paste_from_buffer (request_data, src_buffer,
                             &start, &end);
        }
@@ -3168,21 +3339,27 @@ clipboard_clipboard_buffer_received (GtkClipboard     *clipboard,
     }
   else
     {
-      /* Request the text selection instead */
-      gtk_clipboard_request_text (clipboard,
-                                  clipboard_text_received,
-                                  data);
+      priv = GTK_TEXT_BUFFER_GET_PRIVATE (request_data->buffer);
+
+      if (gtk_clipboard_wait_is_rich_text_available (clipboard,
+                                                     request_data->buffer))
+        {
+          /* Request rich text */
+          gtk_clipboard_request_rich_text (clipboard,
+                                           request_data->buffer,
+                                           clipboard_rich_text_received,
+                                           data);
+        }
+      else
+        {
+          /* Request the text selection instead */
+          gtk_clipboard_request_text (clipboard,
+                                      clipboard_text_received,
+                                      data);
+        }
     }
 }
 
-static const GtkTargetEntry targets[] = {
-  { "STRING", 0, TARGET_STRING },
-  { "TEXT",   0, TARGET_TEXT },
-  { "COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT },
-  { "UTF8_STRING", 0, TARGET_UTF8_STRING },
-  { "GTK_TEXT_BUFFER_CONTENTS", 0, TARGET_TEXT_BUFFER_CONTENTS }
-};
-
 typedef struct
 {
   GtkClipboard *clipboard;
@@ -3192,7 +3369,13 @@ typedef struct
 static void
 update_selection_clipboards (GtkTextBuffer *buffer)
 {
-  GSList *tmp_list = buffer->selection_clipboards;
+  GtkTextBufferPrivate *priv;
+  GSList               *tmp_list = buffer->selection_clipboards;
+
+  priv = GTK_TEXT_BUFFER_GET_PRIVATE (buffer);
+
+  gtk_text_buffer_get_copy_target_list (buffer);
+
   while (tmp_list)
     {
       GtkTextIter start;
@@ -3214,7 +3397,9 @@ update_selection_clipboards (GtkTextBuffer *buffer)
          /* Even if we already have the selection, we need to update our
           * timestamp.
           */
-         if (!gtk_clipboard_set_with_owner (clipboard, targets, G_N_ELEMENTS (targets),
+         if (!gtk_clipboard_set_with_owner (clipboard,
+                                             priv->copy_target_entries,
+                                             priv->n_copy_target_entries,
                                             clipboard_get_selection_cb,
                                             clipboard_clear_selection_cb,
                                             G_OBJECT (buffer)))
@@ -3525,6 +3710,8 @@ cut_or_copy (GtkTextBuffer *buffer,
              gboolean       interactive,
              gboolean       default_editable)
 {
+  GtkTextBufferPrivate *priv;
+
   /* We prefer to cut the selected region between selection_bound and
    * insertion point. If that region is empty, then we cut the region
    * between the "anchor" and the insertion point (this is for
@@ -3534,7 +3721,11 @@ cut_or_copy (GtkTextBuffer *buffer,
    */
   GtkTextIter start;
   GtkTextIter end;
-  
+
+  priv = GTK_TEXT_BUFFER_GET_PRIVATE (buffer);
+
+  gtk_text_buffer_get_copy_target_list (buffer);
+
   if (!gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
     {
       /* Let's try the anchor thing */
@@ -3560,14 +3751,18 @@ cut_or_copy (GtkTextBuffer *buffer,
       
       gtk_text_buffer_insert_range (contents, &ins, &start, &end);
                                     
-      if (!gtk_clipboard_set_with_data (clipboard, targets, G_N_ELEMENTS (targets),
+      if (!gtk_clipboard_set_with_data (clipboard,
+                                        priv->copy_target_entries,
+                                        priv->n_copy_target_entries,
                                        clipboard_get_contents_cb,
                                        clipboard_clear_contents_cb,
                                        contents))
        g_object_unref (contents);
       else
-       gtk_clipboard_set_can_store (clipboard, (GtkTargetEntry *)targets, G_N_ELEMENTS (targets) -1);
-                                    
+       gtk_clipboard_set_can_store (clipboard,
+                                     priv->copy_target_entries + 1,
+                                     priv->n_copy_target_entries - 1);
+
       if (delete_region_after)
         {
           if (interactive)
@@ -3699,6 +3894,126 @@ gtk_text_buffer_end_user_action (GtkTextBuffer *buffer)
     }
 }
 
+static void
+gtk_text_buffer_free_target_lists (GtkTextBuffer *buffer)
+{
+  GtkTextBufferPrivate *priv = GTK_TEXT_BUFFER_GET_PRIVATE (buffer);
+
+  if (priv->copy_target_list)
+    {
+      gtk_target_list_unref (priv->copy_target_list);
+      priv->copy_target_list = NULL;
+
+      gtk_target_table_free (priv->copy_target_entries,
+                             priv->n_copy_target_entries);
+      priv->copy_target_entries = NULL;
+      priv->n_copy_target_entries = 0;
+    }
+
+  if (priv->paste_target_list)
+    {
+      gtk_target_list_unref (priv->paste_target_list);
+      priv->paste_target_list = NULL;
+
+      gtk_target_table_free (priv->paste_target_entries,
+                             priv->n_paste_target_entries);
+      priv->paste_target_entries = NULL;
+      priv->n_paste_target_entries = 0;
+    }
+}
+
+static GtkTargetList *
+gtk_text_buffer_get_target_list (GtkTextBuffer   *buffer,
+                                 gboolean         deserializable,
+                                 GtkTargetEntry **entries,
+                                 gint            *n_entries)
+{
+  GtkTargetList *target_list;
+
+  target_list = gtk_target_list_new (NULL, 0);
+
+  gtk_target_list_add (target_list,
+                       gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"),
+                       GTK_TARGET_SAME_APP,
+                       GTK_TEXT_BUFFER_TARGET_INFO_BUFFER_CONTENTS);
+
+  gtk_target_list_add_rich_text_targets (target_list,
+                                         GTK_TEXT_BUFFER_TARGET_INFO_RICH_TEXT,
+                                         deserializable,
+                                         buffer);
+
+  gtk_target_list_add_text_targets (target_list,
+                                    GTK_TEXT_BUFFER_TARGET_INFO_TEXT);
+
+  *entries = gtk_target_table_new_from_list (target_list, n_entries);
+
+  return target_list;
+}
+
+/**
+ * gtk_text_buffer_get_copy_target_list:
+ * @buffer: a #GtkTextBuffer
+ *
+ * This function returns the list of targets this text buffer can
+ * provide for copying and as DND source. The targets in the list are
+ * added with %info values from the #GtkTextBufferTargetInfo enum,
+ * using gtk_target_list_add_rich_text_targets() and
+ * gtk_target_list_add_text_targets()
+ *
+ * Return value: the #GtkTargetList
+ *
+ * Since: 2.10
+ **/
+GtkTargetList *
+gtk_text_buffer_get_copy_target_list (GtkTextBuffer *buffer)
+{
+  GtkTextBufferPrivate *priv;
+
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
+
+  priv = GTK_TEXT_BUFFER_GET_PRIVATE (buffer);
+
+  if (! priv->copy_target_list)
+    priv->copy_target_list =
+      gtk_text_buffer_get_target_list (buffer, FALSE,
+                                       &priv->copy_target_entries,
+                                       &priv->n_copy_target_entries);
+
+  return priv->copy_target_list;
+}
+
+/**
+ * gtk_text_buffer_get_paste_target_list:
+ * @buffer: a #GtkTextBuffer
+ *
+ * This function returns the list of targets this text buffer supports
+ * for pasting and as DND destination. The targets in the list are
+ * added with %info values from the #GtkTextBufferTargetInfo enum,
+ * using gtk_target_list_add_rich_text_targets() and
+ * gtk_target_list_add_text_targets()
+ *
+ * Return value: the #GtkTargetList
+ *
+ * Since: 2.10
+ **/
+GtkTargetList *
+gtk_text_buffer_get_paste_target_list (GtkTextBuffer *buffer)
+{
+  GtkTextBufferPrivate *priv;
+
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
+
+  priv = GTK_TEXT_BUFFER_GET_PRIVATE (buffer);
+
+  if (! priv->paste_target_list)
+    priv->paste_target_list =
+      gtk_text_buffer_get_target_list (buffer, TRUE,
+                                       &priv->paste_target_entries,
+                                       &priv->n_paste_target_entries);
+
+  return priv->paste_target_list;
+}
+
 /*
  * Logical attribute cache
  */
index 04aac94f0a0ee46e550f1ca5762e9d640c609aa2..7d69a600d51b3e9a6c160350864f669d651f13a6 100644 (file)
@@ -41,6 +41,16 @@ G_BEGIN_DECLS
  * GtkTextBTree is the PRIVATE internal representation of it.
  */
 
+/* these values are used as "info" for the targets contained in the
+ * lists returned by gtk_text_buffer_get_copy,paste_target_list()
+ */
+typedef enum
+{
+  GTK_TEXT_BUFFER_TARGET_INFO_BUFFER_CONTENTS,
+  GTK_TEXT_BUFFER_TARGET_INFO_RICH_TEXT,
+  GTK_TEXT_BUFFER_TARGET_INFO_TEXT
+} GtkTextBufferTargetInfo;
+
 typedef struct _GtkTextBTree GtkTextBTree;
 
 typedef struct _GtkTextLogAttrCache GtkTextLogAttrCache;
@@ -367,6 +377,9 @@ gboolean        gtk_text_buffer_delete_selection        (GtkTextBuffer *buffer,
 void            gtk_text_buffer_begin_user_action       (GtkTextBuffer *buffer);
 void            gtk_text_buffer_end_user_action         (GtkTextBuffer *buffer);
 
+GtkTargetList * gtk_text_buffer_get_copy_target_list    (GtkTextBuffer *buffer);
+GtkTargetList * gtk_text_buffer_get_paste_target_list   (GtkTextBuffer *buffer);
+
 /* INTERNAL private stuff */
 void            _gtk_text_buffer_spew                  (GtkTextBuffer      *buffer);
 
diff --git a/gtk/gtktextbufferrichtext.c b/gtk/gtktextbufferrichtext.c
new file mode 100644 (file)
index 0000000..12cb00b
--- /dev/null
@@ -0,0 +1,704 @@
+/* gtkrichtext.c
+ *
+ * Copyright (C) 2006 Imendio AB
+ * Contact: Michael Natterer <mitch@imendio.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "gtktextbufferrichtext.h"
+#include "gtktextbufferserialize.h"
+#include "gtkalias.h"
+#include "gtkintl.h"
+
+
+typedef struct
+{
+  gchar          *mime_type;
+  gboolean        can_create_tags;
+  GdkAtom         atom;
+  gpointer        function;
+  gpointer        user_data;
+  GDestroyNotify  user_data_destroy;
+} GtkRichTextFormat;
+
+
+static GList   * register_format   (GList             *formats,
+                                    const gchar       *mime_type,
+                                    gpointer           function,
+                                    gpointer           user_data,
+                                    GDestroyNotify     user_data_destroy,
+                                    GdkAtom           *atom);
+static GList   * unregister_format (GList             *formats,
+                                    GdkAtom            atom);
+static GdkAtom * get_formats       (GList             *formats,
+                                    gint              *n_formats);
+static void      free_format       (GtkRichTextFormat *format);
+static void      free_format_list  (GList             *formats);
+static GQuark    serialize_quark   (void);
+static GQuark    deserialize_quark (void);
+
+
+/**
+ * gtk_text_buffer_register_serialize_format:
+ * @buffer: a #GtkTextBuffer
+ * @mime_type: the format's mime-type
+ * @function: the serialize function to register
+ * @user_data: %function's user_data
+ * @user_data_destroy: a function to call when user_data is no longer needed
+ *
+ * This function registers a rich text serialization %function along with
+ * its %mime_type with the passed %buffer.
+ *
+ * Return value: the #GdkAtom that corresponds to the newly registered
+ *               format's mime-type.
+ *
+ * Since: 2.10
+ **/
+GdkAtom
+gtk_text_buffer_register_serialize_format (GtkTextBuffer              *buffer,
+                                           const gchar                *mime_type,
+                                           GtkTextBufferSerializeFunc  function,
+                                           gpointer                    user_data,
+                                           GDestroyNotify              user_data_destroy)
+{
+  GList   *formats;
+  GdkAtom  atom;
+
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), GDK_NONE);
+  g_return_val_if_fail (mime_type != NULL && *mime_type != '\0', GDK_NONE);
+  g_return_val_if_fail (function != NULL, GDK_NONE);
+
+  formats = g_object_steal_qdata (G_OBJECT (buffer), serialize_quark ());
+
+  formats = register_format (formats, mime_type,
+                             (gpointer) function,
+                             user_data, user_data_destroy,
+                             &atom);
+
+  g_object_set_qdata_full (G_OBJECT (buffer), serialize_quark (),
+                           formats, (GDestroyNotify) free_format_list);
+
+  g_object_notify (G_OBJECT (buffer), "copy-target-list");
+
+  return atom;
+}
+
+/**
+ * gtk_text_buffer_register_serialize_tagset:
+ * @buffer: a #GtkTextBuffer
+ * @tagset_name: an optional tagset name, on %NULL
+ *
+ * This function registers GTK+'s internal rich text serialization
+ * format with the passed %buffer. The internal format does not comply
+ * to any standard rich text format and only works between #GtkTextBuffer
+ * instances. It is capable of serializing all of a text buffer's tags
+ * and embedded pixbufs.
+ *
+ * This function is just a wrapper around
+ * gtk_text_buffer_register_serialize_format(). The %mime_type used
+ * for registering is "application/x-gtk-text-buffer-rich-text", or
+ * "application/x-gtk-text-buffer-rich-text;format=%tagset_name" if a
+ * %tagset_name was passed.
+ *
+ * The %tagset_name can be used to restrict the transfer of rich text
+ * to buffers with compatible sets of tags, in order to avoid unknown
+ * tags from being pasted. It is probably the common case to pass an
+ * identifier != %NULL here, since the %NULL tagset requires the
+ * receiving buffer to deal with with pasting of arbitrary tags.
+ *
+ * Return value: the #GdkAtom that corresponds to the newly registered
+ *               format's mime-type.
+ *
+ * Since: 2.10
+ **/
+GdkAtom
+gtk_text_buffer_register_serialize_tagset (GtkTextBuffer *buffer,
+                                           const gchar   *tagset_name)
+{
+  gchar   *mime_type = "application/x-gtk-text-buffer-rich-text";
+  GdkAtom  format;
+
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), GDK_NONE);
+  g_return_val_if_fail (tagset_name == NULL || *tagset_name != '\0', GDK_NONE);
+
+  if (tagset_name)
+    mime_type =
+      g_strdup_printf ("application/x-gtk-text-buffer-rich-text;format=%s",
+                       tagset_name);
+
+  format = gtk_text_buffer_register_serialize_format (buffer, mime_type,
+                                                      _gtk_text_buffer_serialize_rich_text,
+                                                      NULL, NULL);
+
+  if (tagset_name)
+    g_free (mime_type);
+
+  return format;
+}
+
+/**
+ * gtk_text_buffer_register_deserialize_format:
+ * @buffer: a #GtkTextBuffer
+ * @mime_type: the format's mime-type
+ * @function: the deserialize function to register
+ * @user_data: %function's user_data
+ * @user_data_destroy: a function to call when user_data is no longer needed
+ *
+ * This function registers a rich text deserialization %function along with
+ * its %mime_type with the passed %buffer.
+ *
+ * Return value: the #GdkAtom that corresponds to the newly registered
+ *               format's mime-type.
+ *
+ * Since: 2.10
+ **/
+GdkAtom
+gtk_text_buffer_register_deserialize_format (GtkTextBuffer                *buffer,
+                                             const gchar                  *mime_type,
+                                             GtkTextBufferDeserializeFunc  function,
+                                             gpointer                      user_data,
+                                             GDestroyNotify                user_data_destroy)
+{
+  GList   *formats;
+  GdkAtom  atom;
+
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), GDK_NONE);
+  g_return_val_if_fail (mime_type != NULL && *mime_type != '\0', GDK_NONE);
+  g_return_val_if_fail (function != NULL, GDK_NONE);
+
+  formats = g_object_steal_qdata (G_OBJECT (buffer), deserialize_quark ());
+
+  formats = register_format (formats, mime_type,
+                             (gpointer) function,
+                             user_data, user_data_destroy,
+                             &atom);
+
+  g_object_set_qdata_full (G_OBJECT (buffer), deserialize_quark (),
+                           formats, (GDestroyNotify) free_format_list);
+
+  g_object_notify (G_OBJECT (buffer), "paste-target-list");
+
+  return atom;
+}
+
+/**
+ * gtk_text_buffer_register_deserialize_tagset:
+ * @buffer: a #GtkTextBuffer
+ * @tagset_name: an optional tagset name, on %NULL
+ *
+ * This function registers GTK+'s internal rich text serialization
+ * format with the passed %buffer. See
+ * gtk_text_buffer_register_serialize_tagset() for details.
+ *
+ * Return value: the #GdkAtom that corresponds to the newly registered
+ *               format's mime-type.
+ *
+ * Since: 2.10
+ **/
+GdkAtom
+gtk_text_buffer_register_deserialize_tagset (GtkTextBuffer *buffer,
+                                             const gchar   *tagset_name)
+{
+  gchar   *mime_type = "application/x-gtk-text-buffer-rich-text";
+  GdkAtom  format;
+
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), GDK_NONE);
+  g_return_val_if_fail (tagset_name == NULL || *tagset_name != '\0', GDK_NONE);
+
+  if (tagset_name)
+    mime_type =
+      g_strdup_printf ("application/x-gtk-text-buffer-rich-text;format=%s",
+                       tagset_name);
+
+  format = gtk_text_buffer_register_deserialize_format (buffer, mime_type,
+                                                        _gtk_text_buffer_deserialize_rich_text,
+                                                        NULL, NULL);
+
+  if (tagset_name)
+    g_free (mime_type);
+
+  return format;
+}
+
+/**
+ * gtk_text_buffer_unregister_serialize_format:
+ * @buffer: a #GtkTextBuffer
+ * @format: a #GdkAtom representing a registered rich text format.
+ *
+ * This function unregisters a rich text format that was previously
+ * registered using gtk_text_buffer_register_serialize_format() or
+ * gtk_text_buffer_register_serialize_tagset()
+ *
+ * Since: 2.10
+ **/
+void
+gtk_text_buffer_unregister_serialize_format (GtkTextBuffer *buffer,
+                                             GdkAtom        format)
+{
+  GList *formats;
+
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+  g_return_if_fail (format != GDK_NONE);
+
+  formats = g_object_steal_qdata (G_OBJECT (buffer), serialize_quark ());
+
+  formats = unregister_format (formats, format);
+
+  g_object_set_qdata_full (G_OBJECT (buffer), serialize_quark (),
+                           formats, (GDestroyNotify) free_format_list);
+
+  g_object_notify (G_OBJECT (buffer), "copy-target-list");
+}
+
+/**
+ * gtk_text_buffer_unregister_deserialize_format:
+ * @buffer: a #GtkTextBuffer
+ * @format: a #GdkAtom representing a registered rich text format.
+ *
+ * This function unregisters a rich text format that was previously
+ * registered using gtk_text_buffer_register_deserialize_format() or
+ * gtk_text_buffer_register_deserialize_tagset()
+ *
+ * Since: 2.10
+ **/
+void
+gtk_text_buffer_unregister_deserialize_format (GtkTextBuffer *buffer,
+                                               GdkAtom        format)
+{
+  GList *formats;
+
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+  g_return_if_fail (format != GDK_NONE);
+
+  formats = g_object_steal_qdata (G_OBJECT (buffer), deserialize_quark ());
+
+  formats = unregister_format (formats, format);
+
+  g_object_set_qdata_full (G_OBJECT (buffer), deserialize_quark (),
+                           formats, (GDestroyNotify) free_format_list);
+
+  g_object_notify (G_OBJECT (buffer), "paste-target-list");
+}
+
+/**
+ * gtk_text_buffer_deserialize_set_can_create_tags:
+ * @buffer: a #GtkTextBuffer
+ * @format: a #GdkAtom representing a registered rich text format
+ * @can_create_tags: whether deserializing this format may create tags
+ *
+ * Use this function to allow a rich text deserialization function to
+ * create new tags in the receiving buffer. Note that using this
+ * function is almost always a bad idea, because the rich text
+ * functions you register should know how to map the rich text format
+ * they handler to your text buffers set of tags.
+ *
+ * The ability of creating new (arbitrary!) tags in the receiving buffer
+ * is meant for special rich text formats like the internal one that
+ * is registered using gtk_text_buffer_register_deserialize_tagset(),
+ * because that format is essentially a dump of the internal structure
+ * of the source buffer, including its tag names.
+ *
+ * You should allow creation of tags only if you know what you are
+ * doing, e.g. if you defined a tagset name for your application
+ * suite's text buffers and you know that it's fine to receive new
+ * tags from these buffers, because you know that your application can
+ * handle the newly created tags.
+ *
+ * Since: 2.10
+ **/
+void
+gtk_text_buffer_deserialize_set_can_create_tags (GtkTextBuffer *buffer,
+                                                 GdkAtom        format,
+                                                 gboolean       can_create_tags)
+{
+  GList *formats;
+  GList *list;
+  gchar *format_name;
+
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+  g_return_if_fail (format != GDK_NONE);
+
+  formats = g_object_get_qdata (G_OBJECT (buffer), deserialize_quark ());
+
+  for (list = formats; list; list = g_list_next (list))
+    {
+      GtkRichTextFormat *fmt = list->data;
+
+      if (fmt->atom == format)
+        {
+          fmt->can_create_tags = can_create_tags ? TRUE : FALSE;
+          return;
+        }
+    }
+
+  format_name = gdk_atom_name (format);
+  g_warning ("%s: \"%s\" is not registered as deserializable format "
+             "with text buffer %p",
+             G_STRFUNC, format_name ? format_name : "not a GdkAtom", buffer);
+  g_free (format_name);
+}
+
+/**
+ * gtk_text_buffer_deserialize_get_can_create_tags:
+ * @buffer: a #GtkTextBuffer
+ * @format: a #GdkAtom representing a registered rich text format
+ *
+ * This functions returns the value set with
+ * gtk_text_buffer_deserialize_set_can_create_tags()
+ *
+ * Return value: whether deserializing this format may create tags
+ *
+ * Since: 2.10
+ **/
+gboolean
+gtk_text_buffer_deserialize_get_can_create_tags (GtkTextBuffer *buffer,
+                                                 GdkAtom        format)
+{
+  GList *formats;
+  GList *list;
+  gchar *format_name;
+
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE);
+  g_return_val_if_fail (format != GDK_NONE, FALSE);
+
+  formats = g_object_get_qdata (G_OBJECT (buffer), deserialize_quark ());
+
+  for (list = formats; list; list = g_list_next (list))
+    {
+      GtkRichTextFormat *fmt = list->data;
+
+      if (fmt->atom == format)
+        {
+          return fmt->can_create_tags;
+        }
+    }
+
+  format_name = gdk_atom_name (format);
+  g_warning ("%s: \"%s\" is not registered as deserializable format "
+             "with text buffer %p",
+             G_STRFUNC, format_name ? format_name : "not a GdkAtom", buffer);
+  g_free (format_name);
+
+  return FALSE;
+}
+
+/**
+ * gtk_text_buffer_get_serialize_formats:
+ * @buffer: a #GtkTextBuffer
+ * @n_formats: return location for the number of formats
+ *
+ * This function returns the rich text serialize formats registered
+ * with %buffer using gtk_text_buffer_register_serialize_format() or
+ * gtk_text_buffer_register_serialize_tagset()
+ *
+ * Return value: an array of #GdkAtom<!-- -->s representing the registered
+ *               formats.
+ *
+ * Since: 2.10
+ **/
+GdkAtom *
+gtk_text_buffer_get_serialize_formats (GtkTextBuffer *buffer,
+                                       gint          *n_formats)
+{
+  GList *formats;
+
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
+  g_return_val_if_fail (n_formats != NULL, NULL);
+
+  formats = g_object_get_qdata (G_OBJECT (buffer), serialize_quark ());
+
+  return get_formats (formats, n_formats);
+}
+
+/**
+ * gtk_text_buffer_get_deserialize_formats:
+ * @buffer: a #GtkTextBuffer
+ * @n_formats: return location for the number of formats
+ *
+ * This function returns the rich text deserialize formats registered
+ * with %buffer using gtk_text_buffer_register_deserialize_format() or
+ * gtk_text_buffer_register_deserialize_tagset()
+ *
+ * Return value: an array of #GdkAtom<!-- -->s representing the registered
+ *               formats.
+ *
+ * Since: 2.10
+ **/
+GdkAtom *
+gtk_text_buffer_get_deserialize_formats (GtkTextBuffer *buffer,
+                                         gint          *n_formats)
+{
+  GList *formats;
+
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
+  g_return_val_if_fail (n_formats != NULL, NULL);
+
+  formats = g_object_get_qdata (G_OBJECT (buffer), deserialize_quark ());
+
+  return get_formats (formats, n_formats);
+}
+
+/**
+ * gtk_text_buffer_serialize:
+ * @register_buffer: the #GtkTextBuffer %format is registered with
+ * @content_buffer: the #GtkTextBuffer to serialize
+ * @format: the rich text format to use for serializing
+ * @start: start of block of text to serialize
+ * @end: end of block of test to serialize
+ * @length: return location for the length of the serialized data
+ *
+ * This function serializes the portion of text between %start
+ * and %end in the rich text format represented by %format.
+ *
+ * %format<!-- -->s to be used must be registered using
+ * gtk_text_buffer_register_serialize_format() or
+ * gtk_text_buffer_register_serialize_tagset() beforehand.
+ *
+ * Return value: the serialized data, encoded as %format
+ *
+ * Since: 2.10
+ **/
+guint8 *
+gtk_text_buffer_serialize (GtkTextBuffer     *register_buffer,
+                           GtkTextBuffer     *content_buffer,
+                           GdkAtom            format,
+                           const GtkTextIter *start,
+                           const GtkTextIter *end,
+                           gsize             *length)
+{
+  GList *formats;
+  GList *list;
+
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (register_buffer), NULL);
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (content_buffer), NULL);
+  g_return_val_if_fail (format != GDK_NONE, NULL);
+  g_return_val_if_fail (start != NULL, NULL);
+  g_return_val_if_fail (end != NULL, NULL);
+  g_return_val_if_fail (length != NULL, NULL);
+
+  *length = 0;
+
+  formats = g_object_get_qdata (G_OBJECT (register_buffer),
+                                serialize_quark ());
+
+  for (list = formats; list; list = g_list_next (list))
+    {
+      GtkRichTextFormat *fmt = list->data;
+
+      if (fmt->atom == format)
+        {
+          GtkTextBufferSerializeFunc function = fmt->function;
+
+          return function (register_buffer, content_buffer,
+                           start, end, length, fmt->user_data);
+        }
+    }
+
+  return NULL;
+}
+
+/**
+ * gtk_text_buffer_serialize:
+ * @register_buffer: the #GtkTextBuffer %format is registered with
+ * @content_buffer: the #GtkTextBuffer to deserialize into
+ * @format: the rich text format to use for deserializing
+ * @iter: insertion point for the deserialized text
+ * @data: data to deserialize
+ * @length: length of %data
+ * @error: return loaction for a #GError
+ *
+ * This function deserializes rich text in format %format and inserts
+ * it at %iter.
+ *
+ * %format<!-- -->s to be used must be registered using
+ * gtk_text_buffer_register_deserialize_format() or
+ * gtk_text_buffer_register_deserialize_tagset() beforehand.
+ *
+ * Return value: %TRUE on success, %FALSE otherwise.
+ *
+ * Since: 2.10
+ **/
+gboolean
+gtk_text_buffer_deserialize (GtkTextBuffer  *register_buffer,
+                             GtkTextBuffer  *content_buffer,
+                             GdkAtom         format,
+                             GtkTextIter    *iter,
+                             const guint8   *data,
+                             gsize           length,
+                             GError        **error)
+{
+  GList    *formats;
+  GList    *list;
+
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (register_buffer), FALSE);
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (content_buffer), FALSE);
+  g_return_val_if_fail (format != GDK_NONE, FALSE);
+  g_return_val_if_fail (iter != NULL, FALSE);
+  g_return_val_if_fail (data != NULL, FALSE);
+  g_return_val_if_fail (length > 0, FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  formats = g_object_get_qdata (G_OBJECT (register_buffer),
+                                deserialize_quark ());
+
+  for (list = formats; list; list = g_list_next (list))
+    {
+      GtkRichTextFormat *fmt = list->data;
+
+      if (fmt->atom == format)
+        {
+          GtkTextBufferDeserializeFunc function = fmt->function;
+          gboolean                     success;
+
+          success = function (register_buffer, content_buffer,
+                              iter, data, length,
+                              fmt->can_create_tags,
+                              fmt->user_data,
+                              error);
+
+          if (!success && error != NULL && *error == NULL)
+            g_set_error (error, 0, 0,
+                         _("Unknown error when trying to deserialize %s"),
+                         gdk_atom_name (format));
+
+          return success;
+        }
+    }
+
+  g_set_error (error, 0, 0,
+               _("No deserialize function found for format %s"),
+               gdk_atom_name (format));
+
+  return FALSE;
+}
+
+
+/*  private functions  */
+
+static GList *
+register_format (GList          *formats,
+                 const gchar    *mime_type,
+                 gpointer        function,
+                 gpointer        user_data,
+                 GDestroyNotify  user_data_destroy,
+                 GdkAtom        *atom)
+{
+  GtkRichTextFormat *format;
+
+  *atom = gdk_atom_intern (mime_type, FALSE);
+
+  formats = unregister_format (formats, *atom);
+
+  format = g_new0 (GtkRichTextFormat, 1);
+
+  format->mime_type         = g_strdup (mime_type);
+  format->can_create_tags   = FALSE;
+  format->atom              = *atom;
+  format->function          = function;
+  format->user_data         = user_data;
+  format->user_data_destroy = user_data_destroy;
+
+  return g_list_append (formats, format);
+}
+
+static GList *
+unregister_format (GList   *formats,
+                   GdkAtom  atom)
+{
+  GList *list;
+
+  for (list = formats; list; list = g_list_next (list))
+    {
+      GtkRichTextFormat *format = list->data;
+
+      if (format->atom == atom)
+        {
+          free_format (format);
+
+          return g_list_delete_link (formats, list);
+        }
+    }
+
+  return formats;
+}
+
+static GdkAtom *
+get_formats (GList *formats,
+             gint  *n_formats)
+{
+  GdkAtom *array;
+  GList   *list;
+  gint     i;
+
+  *n_formats = g_list_length (formats);
+  array = g_new0 (GdkAtom, *n_formats);
+
+  for (list = formats, i = 0; list; list = g_list_next (list), i++)
+    {
+      GtkRichTextFormat *format = list->data;
+
+      array[i] = format->atom;
+    }
+
+  return array;
+}
+
+static void
+free_format (GtkRichTextFormat *format)
+{
+  if (format->user_data_destroy)
+    format->user_data_destroy (format->user_data);
+
+  g_free (format->mime_type);
+  g_free (format);
+}
+
+static void
+free_format_list (GList *formats)
+{
+  g_list_foreach (formats, (GFunc) free_format, NULL);
+  g_list_free (formats);
+}
+
+static GQuark
+serialize_quark (void)
+{
+  static GQuark quark = 0;
+
+  if (! quark)
+    quark = g_quark_from_static_string ("gtk-text-buffer-serialize-formats");
+
+  return quark;
+}
+
+static GQuark
+deserialize_quark (void)
+{
+  static GQuark quark = 0;
+
+  if (! quark)
+    quark = g_quark_from_static_string ("gtk-text-buffer-deserialize-formats");
+
+  return quark;
+}
+
+#define __GTK_TEXT_BUFFER_RICH_TEXT_C__
+#include "gtkaliasdef.c"
diff --git a/gtk/gtktextbufferrichtext.h b/gtk/gtktextbufferrichtext.h
new file mode 100644 (file)
index 0000000..96fc3c9
--- /dev/null
@@ -0,0 +1,92 @@
+/* gtkrichtext.h
+ *
+ * Copyright (C) 2006 Imendio AB
+ * Contact: Michael Natterer <mitch@imendio.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GTK_TEXT_BUFFER_RICH_TEXT_H__
+#define __GTK_TEXT_BUFFER_RICH_TEXT_H__
+
+#include <gtk/gtktextbuffer.h>
+
+G_BEGIN_DECLS
+
+typedef guint8 * (* GtkTextBufferSerializeFunc)   (GtkTextBuffer     *register_buffer,
+                                                   GtkTextBuffer     *content_buffer,
+                                                   const GtkTextIter *start,
+                                                   const GtkTextIter *end,
+                                                   gsize             *length,
+                                                   gpointer           user_data);
+typedef gboolean (* GtkTextBufferDeserializeFunc) (GtkTextBuffer     *register_buffer,
+                                                   GtkTextBuffer     *content_buffer,
+                                                   GtkTextIter       *iter,
+                                                   const guint8      *data,
+                                                   gsize              length,
+                                                   gboolean           create_tags,
+                                                   gpointer           user_data,
+                                                   GError           **error);
+
+GdkAtom   gtk_text_buffer_register_serialize_format   (GtkTextBuffer                *buffer,
+                                                       const gchar                  *mime_type,
+                                                       GtkTextBufferSerializeFunc    function,
+                                                       gpointer                      user_data,
+                                                       GDestroyNotify                user_data_destroy);
+GdkAtom   gtk_text_buffer_register_serialize_tagset   (GtkTextBuffer                *buffer,
+                                                       const gchar                  *tagset_name);
+
+GdkAtom   gtk_text_buffer_register_deserialize_format (GtkTextBuffer                *buffer,
+                                                       const gchar                  *mime_type,
+                                                       GtkTextBufferDeserializeFunc  function,
+                                                       gpointer                      user_data,
+                                                       GDestroyNotify                user_data_destroy);
+GdkAtom   gtk_text_buffer_register_deserialize_tagset (GtkTextBuffer                *buffer,
+                                                       const gchar                  *tagset_name);
+
+void    gtk_text_buffer_unregister_serialize_format   (GtkTextBuffer                *buffer,
+                                                       GdkAtom                       format);
+void    gtk_text_buffer_unregister_deserialize_format (GtkTextBuffer                *buffer,
+                                                       GdkAtom                       format);
+
+void     gtk_text_buffer_deserialize_set_can_create_tags (GtkTextBuffer             *buffer,
+                                                          GdkAtom                    format,
+                                                          gboolean                   can_create_tags);
+gboolean gtk_text_buffer_deserialize_get_can_create_tags (GtkTextBuffer             *buffer,
+                                                          GdkAtom                    format);
+
+GdkAtom * gtk_text_buffer_get_serialize_formats       (GtkTextBuffer                *buffer,
+                                                       gint                         *n_formats);
+GdkAtom * gtk_text_buffer_get_deserialize_formats     (GtkTextBuffer                *buffer,
+                                                       gint                         *n_formats);
+
+guint8  * gtk_text_buffer_serialize                   (GtkTextBuffer                *register_buffer,
+                                                       GtkTextBuffer                *content_buffer,
+                                                       GdkAtom                       format,
+                                                       const GtkTextIter            *start,
+                                                       const GtkTextIter            *end,
+                                                       gsize                        *length);
+gboolean  gtk_text_buffer_deserialize                 (GtkTextBuffer                *register_buffer,
+                                                       GtkTextBuffer                *content_buffer,
+                                                       GdkAtom                       format,
+                                                       GtkTextIter                  *iter,
+                                                       const guint8                 *data,
+                                                       gsize                         length,
+                                                       GError                      **error);
+
+G_END_DECLS
+
+#endif /* __GTK_TEXT_BUFFER_RICH_TEXT_H__ */
diff --git a/gtk/gtktextbufferserialize.c b/gtk/gtktextbufferserialize.c
new file mode 100644 (file)
index 0000000..4a869d5
--- /dev/null
@@ -0,0 +1,1877 @@
+/* gtktextbufferserialize.c
+ *
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2004 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* FIXME: We should use other error codes for the
+ * parts that deal with the format errors
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "gdk-pixbuf/gdk-pixdata.h"
+#include "gtktextbufferserialize.h"
+
+#include "gtkintl.h"
+
+
+typedef struct
+{
+  GString *tag_table_str;
+  GString *text_str;
+  GHashTable *tags;
+  GtkTextIter start, end;
+
+  gint n_pixbufs;
+  GList *pixbufs;
+  gint tag_id;
+  GHashTable *tag_id_tags;
+} SerializationContext;
+
+static gchar *
+serialize_value (GValue *value)
+{
+  if (g_value_type_transformable (value->g_type, G_TYPE_STRING))
+    {
+      GValue text_value = { 0 };
+      gchar *tmp;
+
+      g_value_init (&text_value, G_TYPE_STRING);
+      g_value_transform (value, &text_value);
+
+      tmp = g_markup_escape_text (g_value_get_string (&text_value), -1);
+      g_value_unset (&text_value);
+
+      return tmp;
+    }
+  else if (value->g_type == GDK_TYPE_COLOR)
+    {
+      GdkColor *color = g_value_get_boxed (value);
+
+      return g_strdup_printf ("%x:%x:%x", color->red, color->green, color->blue);
+    }
+  else if (g_type_is_a (value->g_type, GDK_TYPE_DRAWABLE))
+    {
+      /* Don't do anything */
+    }
+  else
+    {
+      g_warning ("Type %s is not serializable\n", g_type_name (value->g_type));
+    }
+
+  return NULL;
+}
+
+static gboolean
+deserialize_value (const gchar *str,
+                   GValue      *value)
+{
+  if (g_value_type_transformable (G_TYPE_STRING, value->g_type))
+    {
+      GValue text_value = { 0 };
+      gboolean retval;
+
+      g_value_init (&text_value, G_TYPE_STRING);
+      g_value_set_static_string (&text_value, str);
+
+      retval = g_value_transform (&text_value, value);
+      g_value_unset (&text_value);
+
+      return retval;
+    }
+  else if (value->g_type == G_TYPE_BOOLEAN)
+    {
+      gboolean v;
+
+      v = strcmp (str, "TRUE") == 0;
+
+      g_value_set_boolean (value, v);
+
+      return TRUE;
+    }
+  else if (value->g_type == G_TYPE_INT)
+    {
+      gchar *tmp;
+      int v;
+
+      v = strtol (str, &tmp, 10);
+
+      if (tmp == NULL || tmp == str)
+       return FALSE;
+
+      g_value_set_int (value, v);
+
+      return TRUE;
+    }
+  else if (value->g_type == G_TYPE_DOUBLE)
+    {
+      gchar *tmp;
+      gdouble v;
+
+      v = g_ascii_strtod (str, &tmp);
+
+      if (tmp == NULL || tmp == str)
+       return FALSE;
+
+      g_value_set_double (value, v);
+
+      return TRUE;
+    }
+  else if (value->g_type == GDK_TYPE_COLOR)
+    {
+      GdkColor color;
+      const gchar *old;
+      gchar *tmp;
+
+      old = str;
+      color.red = strtol (old, &tmp, 16);
+
+      if (tmp == NULL || tmp == old)
+       return FALSE;
+
+      old = tmp;
+      if (*old++ != ':')
+       return FALSE;
+
+      color.green = strtol (old, &tmp, 16);
+      if (tmp == NULL || tmp == old)
+       return FALSE;
+
+      old = tmp;
+      if (*old++ != ':')
+       return FALSE;
+
+      color.blue = strtol (old, &tmp, 16);
+
+      if (tmp == NULL || tmp == old || *tmp != '\0')
+       return FALSE;
+
+      g_value_set_boxed (value, &color);
+
+      return TRUE;
+    }
+  else if (G_VALUE_HOLDS_ENUM (value))
+    {
+      GEnumClass *class = G_ENUM_CLASS (g_type_class_peek (value->g_type));
+      GEnumValue *enum_value;
+
+      enum_value = g_enum_get_value_by_name (class, str);
+
+      if (enum_value)
+       {
+         g_value_set_enum (value, enum_value->value);
+         return TRUE;
+       }
+
+      return FALSE;
+    }
+  else
+    {
+      g_warning ("Type %s can not be deserialized\n", g_type_name (value->g_type));
+    }
+
+  return FALSE;
+}
+
+/* Checks if a param is set, or if it's the default value */
+static gboolean
+is_param_set (GObject    *object,
+              GParamSpec *pspec,
+              GValue     *value)
+{
+  /* We need to special case some attributes here */
+  if (strcmp (pspec->name, "background-gdk") == 0)
+    {
+      gboolean is_set;
+
+      g_object_get (object, "background-set", &is_set, NULL);
+
+      if (is_set)
+       {
+         g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+
+         g_object_get_property (object, pspec->name, value);
+
+         return TRUE;
+       }
+
+      return FALSE;
+    }
+  else if (strcmp (pspec->name, "foreground-gdk") == 0)
+    {
+      gboolean is_set;
+
+      g_object_get (object, "foreground-set", &is_set, NULL);
+
+      if (is_set)
+       {
+         g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+
+         g_object_get_property (object, pspec->name, value);
+
+         return TRUE;
+       }
+
+      return FALSE;
+    }
+  else
+    {
+      gboolean is_set;
+      gchar *is_set_name;
+
+      is_set_name = g_strdup_printf ("%s-set", pspec->name);
+
+      if (g_object_class_find_property (G_OBJECT_GET_CLASS (object), is_set_name) == NULL)
+       {
+         g_free (is_set_name);
+         return FALSE;
+       }
+      else
+       {
+         g_object_get (object, is_set_name, &is_set, NULL);
+
+         if (!is_set)
+           {
+             g_free (is_set_name);
+             return FALSE;
+           }
+
+         g_free (is_set_name);
+
+         g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+
+         g_object_get_property (object, pspec->name, value);
+
+         if (g_param_value_defaults (pspec, value))
+           {
+             g_value_unset (value);
+
+             return FALSE;
+           }
+       }
+      return TRUE;
+    }
+}
+
+static void
+serialize_tag (gpointer key,
+               gpointer data,
+               gpointer user_data)
+{
+  SerializationContext *context = user_data;
+  GtkTextTag *tag = data;
+  gchar *tag_name;
+  gint tag_id;
+  GParamSpec **pspecs;
+  guint n_pspecs;
+  int i;
+
+  g_string_append (context->tag_table_str, "  <tag ");
+
+  /* Handle anonymous tags */
+  if (tag->name)
+    {
+      tag_name = g_markup_escape_text (tag->name, -1);
+      g_string_append_printf (context->tag_table_str, "name=\"%s\"", tag_name);
+      g_free (tag_name);
+    }
+  else
+    {
+      tag_id = GPOINTER_TO_INT (g_hash_table_lookup (context->tag_id_tags, tag));
+
+      g_string_append_printf (context->tag_table_str, "id=\"%d\"", tag_id);
+    }
+
+  g_string_append_printf (context->tag_table_str, " priority=\"%d\">\n", tag->priority);
+
+  /* Serialize properties */
+  pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (tag), &n_pspecs);
+
+  for (i = 0; i < n_pspecs; i++)
+    {
+      GValue value = { 0 };
+      gchar *tmp, *tmp2;
+
+      if (!(pspecs[i]->flags & G_PARAM_READABLE) ||
+         !(pspecs[i]->flags & G_PARAM_WRITABLE))
+       continue;
+
+      if (!is_param_set (G_OBJECT (tag), pspecs[i], &value))
+       continue;
+
+      /* Now serialize the attr */
+      tmp2 = serialize_value (&value);
+
+      if (tmp2)
+       {
+         tmp = g_markup_escape_text (pspecs[i]->name, -1);
+         g_string_append_printf (context->tag_table_str, "   <attr name=\"%s\" ", tmp);
+         g_free (tmp);
+
+         tmp = g_markup_escape_text (g_type_name (pspecs[i]->value_type), -1);
+         g_string_append_printf (context->tag_table_str, "type=\"%s\" value=\"%s\" />\n", tmp, tmp2);
+
+         g_free (tmp);
+         g_free (tmp2);
+       }
+
+      g_value_unset (&value);
+    }
+
+  g_free (pspecs);
+
+  g_string_append (context->tag_table_str, "  </tag>\n");
+}
+
+static void
+serialize_tags (SerializationContext *context)
+{
+  g_string_append (context->tag_table_str, " <text_view_markup>\n");
+  g_string_append (context->tag_table_str, " <tags>\n");
+  g_hash_table_foreach (context->tags, serialize_tag, context);
+  g_string_append (context->tag_table_str, " </tags>\n");
+}
+
+#if 0
+static void
+dump_tag_list (const gchar *str,
+               GList       *list)
+{
+  g_print ("%s: ", str);
+
+  if (!list)
+    g_print ("(empty)");
+  else
+    {
+      while (list)
+       {
+         g_print ("%s ", ((GtkTextTag *)list->data)->name);
+         list = list->next;
+       }
+    }
+
+  g_print ("\n");
+}
+#endif
+
+static void
+find_list_delta (GSList  *old_list,
+                 GSList  *new_list,
+                GList  **added,
+                 GList  **removed)
+{
+  GSList *tmp;
+  GList *tmp_added, *tmp_removed;
+
+  tmp_added = NULL;
+  tmp_removed = NULL;
+
+  /* Find added tags */
+  tmp = new_list;
+  while (tmp)
+    {
+      if (!g_slist_find (old_list, tmp->data))
+       tmp_added = g_list_prepend (tmp_added, tmp->data);
+
+      tmp = tmp->next;
+    }
+
+  *added = tmp_added;
+
+  /* Find removed tags */
+  tmp = old_list;
+  while (tmp)
+    {
+      if (!g_slist_find (new_list, tmp->data))
+       tmp_removed = g_list_prepend (tmp_removed, tmp->data);
+
+      tmp = tmp->next;
+    }
+
+  /* We reverse the list here to match the xml semantics */
+  *removed = g_list_reverse (tmp_removed);
+}
+
+static void
+serialize_section_header (GString     *str,
+                         const gchar *name,
+                         gint         length)
+{
+  g_return_if_fail (strlen (name) == 26);
+
+  g_string_append (str, name);
+
+  g_string_append_c (str, length >> 24);
+
+  g_string_append_c (str, (length >> 16) & 0xff);
+  g_string_append_c (str, (length >> 8) & 0xff);
+  g_string_append_c (str, length & 0xff);
+}
+
+static void
+serialize_text (GtkTextBuffer        *buffer,
+                SerializationContext *context)
+{
+  GtkTextIter iter, old_iter;
+  GSList *tag_list, *new_tag_list;
+  GQueue *active_tags;
+  int i;
+
+  g_string_append (context->text_str, "<text>");
+
+  iter = context->start;
+  tag_list = NULL;
+  active_tags = g_queue_new ();
+
+  do
+    {
+      GList *added, *removed;
+      GList *tmp;
+      gchar *tmp_text, *escaped_text;
+
+      new_tag_list = gtk_text_iter_get_tags (&iter);
+      find_list_delta (tag_list, new_tag_list, &added, &removed);
+
+      /* Handle removed tags */
+      tmp = removed;
+      while (tmp)
+       {
+         GtkTextTag *tag = tmp->data;
+
+         g_string_append (context->text_str, "</apply_tag>");
+
+         /* We might need to drop some of the tags and re-add them afterwards */
+         while (g_queue_peek_head (active_tags) != tag &&
+                    !g_queue_is_empty (active_tags))
+           {
+             added = g_list_prepend (added, g_queue_pop_head (active_tags));
+             g_string_append_printf (context->text_str, "</apply_tag>");
+           }
+
+         g_queue_pop_head (active_tags);
+
+         tmp = tmp->next;
+       }
+
+      /* Handle added tags */
+      tmp = added;
+      while (tmp)
+       {
+         GtkTextTag *tag = tmp->data;
+         gchar *tag_name;
+
+         /* Add it to the tag hash table */
+         g_hash_table_insert (context->tags, tag, tag);
+
+         if (tag->name)
+           {
+             tag_name = g_markup_escape_text (tag->name, -1);
+
+             g_string_append_printf (context->text_str, "<apply_tag name=\"%s\">", tag_name);
+             g_free (tag_name);
+           }
+         else
+           {
+             gpointer tag_id;
+
+             /* We've got an anonymous tag, find out if it's been
+                used before */
+             if (!g_hash_table_lookup_extended (context->tag_id_tags, tag, NULL, &tag_id))
+               {
+                 tag_id = GINT_TO_POINTER (context->tag_id++);
+
+                 g_hash_table_insert (context->tag_id_tags, tag, tag_id);
+               }
+
+             g_string_append_printf (context->text_str, "<apply_tag id=\"%d\">", GPOINTER_TO_INT (tag_id));
+           }
+         g_queue_push_head (active_tags, tag);
+
+         tmp = tmp->next;
+       }
+
+      g_slist_free (tag_list);
+      tag_list = new_tag_list;
+
+      old_iter = iter;
+
+      /* Now try to go to either the next tag toggle, or if a pixbuf appears */
+      while (TRUE)
+       {
+         gunichar ch = gtk_text_iter_get_char (&iter);
+
+         if (ch == 0xFFFC)
+           {
+             GdkPixbuf *pixbuf = gtk_text_iter_get_pixbuf (&iter);
+
+             if (pixbuf)
+               {
+                 /* Append the text before the pixbuf */
+                 tmp_text = gtk_text_iter_get_slice (&old_iter, &iter);
+                 escaped_text = g_markup_escape_text (tmp_text, -1);
+                 g_free (tmp_text);
+
+                 /* Forward so we don't get the 0xfffc char */
+                 gtk_text_iter_forward_char (&iter);
+                 old_iter = iter;
+
+                 g_string_append (context->text_str, escaped_text);
+                 g_free (escaped_text);
+
+                 g_string_append_printf (context->text_str, "<pixbuf index=\"%d\" />", context->n_pixbufs);
+
+                 context->n_pixbufs++;
+                 context->pixbufs = g_list_prepend (context->pixbufs, pixbuf);
+               }
+           }
+          else if (ch == 0)
+            {
+                break;
+            }
+         else
+           gtk_text_iter_forward_char (&iter);
+
+         if (gtk_text_iter_toggles_tag (&iter, NULL))
+           break;
+       }
+
+      /* We might have moved too far */
+      if (gtk_text_iter_compare (&iter, &context->end) > 0)
+       iter = context->end;
+
+      /* Append the text */
+      tmp_text = gtk_text_iter_get_slice (&old_iter, &iter);
+      escaped_text = g_markup_escape_text (tmp_text, -1);
+      g_free (tmp_text);
+
+      g_string_append (context->text_str, escaped_text);
+      g_free (escaped_text);
+    }
+  while (!gtk_text_iter_equal (&iter, &context->end));
+
+  /* Close any open tags */
+  for (i = 0; i < g_queue_get_length (active_tags); i++) {
+    g_string_append (context->text_str, "</apply_tag>");
+  }
+  g_queue_free (active_tags);
+  g_string_append (context->text_str, "</text>\n</text_view_markup>\n");
+}
+
+static void
+serialize_pixbufs (SerializationContext *context,
+                  GString              *text)
+{
+  GList *list;
+
+  for (list = context->pixbufs; list != NULL; list = list->next)
+    {
+      GdkPixbuf *pixbuf = list->data;
+      GdkPixdata pixdata;
+      guint8 *tmp;
+      guint len;
+
+      gdk_pixdata_from_pixbuf (&pixdata, pixbuf, FALSE);
+      tmp = gdk_pixdata_serialize (&pixdata, &len);
+
+      serialize_section_header (text, "GTKTEXTBUFFERPIXBDATA-0001", len);
+      g_string_append_len (text, (gchar *) tmp, len);
+      g_free (tmp);
+    }
+}
+
+guint8 *
+_gtk_text_buffer_serialize_rich_text (GtkTextBuffer     *register_buffer,
+                                      GtkTextBuffer     *content_buffer,
+                                      const GtkTextIter *start,
+                                      const GtkTextIter *end,
+                                      gsize             *length,
+                                      gpointer           user_data)
+{
+  SerializationContext context;
+  GString *text;
+
+  context.tags = g_hash_table_new (NULL, NULL);
+  context.text_str = g_string_new (NULL);
+  context.tag_table_str = g_string_new (NULL);
+  context.start = *start;
+  context.end = *end;
+  context.n_pixbufs = 0;
+  context.pixbufs = NULL;
+  context.tag_id = 0;
+  context.tag_id_tags = g_hash_table_new (NULL, NULL);
+
+  /* We need to serialize the text before the tag table so we know
+     what tags are used */
+  serialize_text (content_buffer, &context);
+  serialize_tags (&context);
+
+  text = g_string_new (NULL);
+  serialize_section_header (text, "GTKTEXTBUFFERCONTENTS-0001",
+                            context.tag_table_str->len + context.text_str->len);
+
+  g_string_append_len (text, context.tag_table_str->str, context.tag_table_str->len);
+  g_string_append_len (text, context.text_str->str, context.text_str->len);
+
+  context.pixbufs = g_list_reverse (context.pixbufs);
+  serialize_pixbufs (&context, text);
+
+  g_hash_table_destroy (context.tags);
+  g_list_free (context.pixbufs);
+  g_string_free (context.text_str, TRUE);
+  g_string_free (context.tag_table_str, TRUE);
+  g_hash_table_destroy (context.tag_id_tags);
+
+  *length = text->len;
+
+  return (guint8 *) g_string_free (text, FALSE);
+}
+
+typedef enum
+{
+  STATE_START,
+  STATE_TEXT_VIEW_MARKUP,
+  STATE_TAGS,
+  STATE_TAG,
+  STATE_ATTR,
+  STATE_TEXT,
+  STATE_APPLY_TAG,
+  STATE_PIXBUF
+} ParseState;
+
+typedef struct
+{
+  gchar *text;
+  GdkPixbuf *pixbuf;
+  GSList *tags;
+} TextSpan;
+
+typedef struct
+{
+  GtkTextTag *tag;
+  gint prio;
+} TextTagPrio;
+
+typedef struct
+{
+  GSList *states;
+
+  GList *headers;
+
+  GtkTextBuffer *buffer;
+
+  /* Tags that are defined in <tag> elements */
+  GHashTable *defined_tags;
+
+  /* Tags that are anonymous */
+  GHashTable *anonymous_tags;
+
+  /* Tag name substitutions */
+  GHashTable *substitutions;
+
+  /* Current tag */
+  GtkTextTag *current_tag;
+
+  /* Priority of current tag */
+  gint current_tag_prio;
+
+  /* Id of current tag */
+  gint current_tag_id;
+
+  /* Tags and their priorities */
+  GList *tag_priorities;
+
+  GSList *tag_stack;
+
+  GList *spans;
+
+  gboolean create_tags;
+
+  gboolean parsed_text;
+  gboolean parsed_tags;
+} ParseInfo;
+
+static void
+set_error (GError              **err,
+           GMarkupParseContext  *context,
+           int                   error_domain,
+           int                   error_code,
+           const char           *format,
+           ...)
+{
+  int line, ch;
+  va_list args;
+  char *str;
+
+  g_markup_parse_context_get_position (context, &line, &ch);
+
+  va_start (args, format);
+  str = g_strdup_vprintf (format, args);
+  va_end (args);
+
+  g_set_error (err, error_domain, error_code,
+               ("Line %d character %d: %s"),
+               line, ch, str);
+
+  g_free (str);
+}
+
+static void
+push_state (ParseInfo  *info,
+            ParseState  state)
+{
+  info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state));
+}
+
+static void
+pop_state (ParseInfo *info)
+{
+  g_return_if_fail (info->states != NULL);
+
+  info->states = g_slist_remove (info->states, info->states->data);
+}
+
+static ParseState
+peek_state (ParseInfo *info)
+{
+  g_return_val_if_fail (info->states != NULL, STATE_START);
+
+  return GPOINTER_TO_INT (info->states->data);
+}
+
+#define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0)
+
+
+static gboolean
+check_id_or_name (GMarkupParseContext  *context,
+                 const gchar          *element_name,
+                 const gchar         **attribute_names,
+                 const gchar         **attribute_values,
+                 gint                 *id,
+                 const gchar         **name,
+                 GError              **error)
+{
+  gboolean has_id = FALSE;
+  gboolean has_name = FALSE;
+  int i;
+
+  *id = 0;
+  *name = NULL;
+
+  for (i = 0; attribute_names[i] != NULL; i++)
+    {
+      if (strcmp (attribute_names[i], "name") == 0)
+       {
+         *name = attribute_values[i];
+
+         if (has_id)
+           {
+             set_error (error, context,
+                        G_MARKUP_ERROR,
+                        G_MARKUP_ERROR_PARSE,
+                        _("Both \"id\" and \"name\" were found on the <%s> element"),
+                        element_name);
+             return FALSE;
+           }
+
+         if (has_name)
+           {
+             set_error (error, context,
+                        G_MARKUP_ERROR,
+                        G_MARKUP_ERROR_PARSE,
+                        _("The attribute \"name\" were found twice on the <%s> element"),
+                        element_name);
+             return FALSE;
+           }
+
+         has_name = TRUE;
+       }
+      else if (strcmp (attribute_names[i], "id") == 0)
+       {
+         gchar *tmp;
+
+         if (has_name)
+           {
+             set_error (error, context,
+                        G_MARKUP_ERROR,
+                        G_MARKUP_ERROR_PARSE,
+                        _("Both \"id\" and \"name\" were found on the <%s> element"),
+                        element_name);
+             return FALSE;
+           }
+
+         if (has_id)
+           {
+             set_error (error, context,
+                        G_MARKUP_ERROR,
+                        G_MARKUP_ERROR_PARSE,
+                        _("The attribute \"id\" were found twice on the <%s> element"),
+                        element_name);
+             return FALSE;
+           }
+
+         has_id = TRUE;
+
+         /* Try parsing the integer */
+         *id = strtol (attribute_values[i], &tmp, 10);
+
+         if (tmp == NULL || tmp == attribute_values[i])
+           {
+             set_error (error, context,
+                        G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                        _("<%s> element has invalid id \"%s\""), attribute_values[i]);
+             return FALSE;
+           }
+       }
+    }
+
+  if (!has_id && !has_name)
+    {
+      set_error (error, context,
+                G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                _("<%s> element neither a \"name\" nor an \"id\" element"), element_name);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+typedef struct
+{
+  const char  *name;
+  const char **retloc;
+} LocateAttr;
+
+static gboolean
+locate_attributes (GMarkupParseContext  *context,
+                   const char           *element_name,
+                   const char          **attribute_names,
+                   const char          **attribute_values,
+                  gboolean              allow_unknown_attrs,
+                   GError              **error,
+                   const char           *first_attribute_name,
+                   const char          **first_attribute_retloc,
+                   ...)
+{
+  va_list args;
+  const char *name;
+  const char **retloc;
+  int n_attrs;
+#define MAX_ATTRS 24
+  LocateAttr attrs[MAX_ATTRS];
+  gboolean retval;
+  int i;
+
+  g_return_val_if_fail (first_attribute_name != NULL, FALSE);
+  g_return_val_if_fail (first_attribute_retloc != NULL, FALSE);
+
+  retval = TRUE;
+
+  n_attrs = 1;
+  attrs[0].name = first_attribute_name;
+  attrs[0].retloc = first_attribute_retloc;
+  *first_attribute_retloc = NULL;
+
+  va_start (args, first_attribute_retloc);
+
+  name = va_arg (args, const char*);
+  retloc = va_arg (args, const char**);
+
+  while (name != NULL)
+    {
+      g_return_val_if_fail (retloc != NULL, FALSE);
+
+      g_assert (n_attrs < MAX_ATTRS);
+
+      attrs[n_attrs].name = name;
+      attrs[n_attrs].retloc = retloc;
+      n_attrs += 1;
+      *retloc = NULL;
+
+      name = va_arg (args, const char*);
+      retloc = va_arg (args, const char**);
+    }
+
+  va_end (args);
+
+  if (!retval)
+    return retval;
+
+  i = 0;
+  while (attribute_names[i])
+    {
+      int j;
+      gboolean found;
+
+      found = FALSE;
+      j = 0;
+      while (j < n_attrs)
+        {
+          if (strcmp (attrs[j].name, attribute_names[i]) == 0)
+            {
+              retloc = attrs[j].retloc;
+
+              if (*retloc != NULL)
+                {
+                  set_error (error, context,
+                             G_MARKUP_ERROR,
+                             G_MARKUP_ERROR_PARSE,
+                             _("Attribute \"%s\" repeated twice on the same <%s> element"),
+                             attrs[j].name, element_name);
+                  retval = FALSE;
+                  goto out;
+                }
+
+              *retloc = attribute_values[i];
+              found = TRUE;
+            }
+
+          ++j;
+        }
+
+      if (!found && !allow_unknown_attrs)
+        {
+          set_error (error, context,
+                     G_MARKUP_ERROR,
+                     G_MARKUP_ERROR_PARSE,
+                     _("Attribute \"%s\" is invalid on <%s> element in this context"),
+                     attribute_names[i], element_name);
+          retval = FALSE;
+          goto out;
+        }
+
+      ++i;
+    }
+
+ out:
+  return retval;
+}
+
+static gboolean
+check_no_attributes (GMarkupParseContext  *context,
+                     const char           *element_name,
+                     const char          **attribute_names,
+                     const char          **attribute_values,
+                     GError              **error)
+{
+  if (attribute_names[0] != NULL)
+    {
+      set_error (error, context,
+                 G_MARKUP_ERROR,
+                 G_MARKUP_ERROR_PARSE,
+                 _("Attribute \"%s\" is invalid on <%s> element in this context"),
+                 attribute_names[0], element_name);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static GtkTextTag *
+tag_exists (GMarkupParseContext *context,
+           const gchar         *name,
+           gint                 id,
+           ParseInfo           *info,
+           GError             **error)
+{
+  const gchar *real_name;
+
+  if (info->create_tags)
+    {
+      /* If we have an anonymous tag, just return it directly */
+      if (!name)
+       return g_hash_table_lookup (info->anonymous_tags,
+                                   GINT_TO_POINTER (id));
+
+      /* First, try the substitutions */
+      real_name = g_hash_table_lookup (info->substitutions, name);
+
+      if (real_name)
+       return gtk_text_tag_table_lookup (info->buffer->tag_table, real_name);
+
+      /* Next, try the list of defined tags */
+      if (g_hash_table_lookup (info->defined_tags, name) != NULL)
+       return gtk_text_tag_table_lookup (info->buffer->tag_table, name);
+
+      set_error (error, context,
+                G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                _("Tag \"%s\" has not been defined."), name);
+
+      return NULL;
+    }
+  else
+    {
+      GtkTextTag *tag;
+
+      if (!name)
+       {
+         set_error (error, context,
+                    G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                    _("Anonymous tag found and tags can not be created."));
+         return NULL;
+       }
+
+      tag = gtk_text_tag_table_lookup (info->buffer->tag_table, name);
+
+      if (tag)
+       return tag;
+
+      set_error (error, context,
+                G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                _("Tag \"%s\" does not exist in buffer and tags can not be created."), name);
+
+      return NULL;
+    }
+}
+
+typedef struct
+{
+  const gchar *id;
+  gint length;
+  const gchar *start;
+} Header;
+
+static GdkPixbuf *
+get_pixbuf_from_headers (GList   *headers,
+                         int      id,
+                         GError **error)
+{
+  Header *header;
+  GdkPixdata pixdata;
+  GdkPixbuf *pixbuf;
+
+  header = g_list_nth_data (headers, id);
+
+  if (!header)
+    return NULL;
+
+  if (!gdk_pixdata_deserialize (&pixdata, header->length,
+                                (const guint8 *) header->start, error))
+    return NULL;
+
+  pixbuf = gdk_pixbuf_from_pixdata (&pixdata, TRUE, error);
+
+  return pixbuf;
+}
+
+static void
+parse_apply_tag_element (GMarkupParseContext  *context,
+                        const gchar          *element_name,
+                        const gchar         **attribute_names,
+                        const gchar         **attribute_values,
+                        ParseInfo            *info,
+                        GError              **error)
+{
+  const gchar *name, *priority;
+  gint id;
+  GtkTextTag *tag;
+
+  g_assert (peek_state (info) == STATE_TEXT ||
+           peek_state (info) == STATE_APPLY_TAG);
+
+  if (ELEMENT_IS ("apply_tag"))
+    {
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values, TRUE, error,
+                             "priority", &priority, NULL))
+       return;
+
+      if (!check_id_or_name (context, element_name, attribute_names, attribute_values,
+                            &id, &name, error))
+       return;
+
+
+      tag = tag_exists (context, name, id, info, error);
+
+      if (!tag)
+       return;
+
+      info->tag_stack = g_slist_prepend (info->tag_stack, tag);
+
+      push_state (info, STATE_APPLY_TAG);
+    }
+  else if (ELEMENT_IS ("pixbuf"))
+    {
+      int int_id;
+      GdkPixbuf *pixbuf;
+      TextSpan *span;
+      const gchar *pixbuf_id;
+
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values, FALSE, error,
+                             "index", &pixbuf_id, NULL))
+       return;
+
+      int_id = atoi (pixbuf_id);
+      pixbuf = get_pixbuf_from_headers (info->headers, int_id, error);
+
+      span = g_new0 (TextSpan, 1);
+      span->pixbuf = pixbuf;
+      span->tags = NULL;
+
+      info->spans = g_list_prepend (info->spans, span);
+
+      if (!pixbuf)
+       return;
+
+      push_state (info, STATE_PIXBUF);
+    }
+  else
+    set_error (error, context,
+              G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+              _("Element <%s> is not allowed below <%s>"),
+              element_name, peek_state(info) == STATE_TEXT ? "text" : "apply_tag");
+}
+
+static void
+parse_attr_element (GMarkupParseContext  *context,
+                   const gchar          *element_name,
+                   const gchar         **attribute_names,
+                   const gchar         **attribute_values,
+                   ParseInfo            *info,
+                   GError              **error)
+{
+  const gchar *name, *type, *value;
+  GType gtype;
+  GValue gvalue = { 0 };
+  GParamSpec *pspec;
+
+  g_assert (peek_state (info) == STATE_TAG);
+
+  if (ELEMENT_IS ("attr"))
+    {
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values, FALSE, error,
+                             "name", &name, "type", &type, "value", &value, NULL))
+       return;
+
+      gtype = g_type_from_name (type);
+
+      if (gtype == G_TYPE_INVALID)
+       {
+         set_error (error, context,
+                    G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                    _("\"%s\" is not a valid attribute type"), type);
+         return;
+       }
+
+      if (!(pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (info->current_tag), name)))
+       {
+         set_error (error, context,
+                    G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                    _("\"%s\" is not a valid attribute name"), name);
+         return;
+       }
+
+      g_value_init (&gvalue, gtype);
+
+      if (!deserialize_value (value, &gvalue))
+       {
+         set_error (error, context,
+                    G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                    _("\"%s\" could not be converted to a value of type \"%s\" for attribute \"%s\""),
+                    value, type, name);
+         return;
+       }
+
+      if (g_param_value_validate (pspec, &gvalue))
+       {
+         set_error (error, context,
+                    G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                    _("\"%s\" is not a valid value of for attribute \"%s\""),
+                    value, name);
+         g_value_unset (&gvalue);
+         return;
+       }
+
+      g_object_set_property (G_OBJECT (info->current_tag),
+                            name, &gvalue);
+
+      g_value_unset (&gvalue);
+
+      push_state (info, STATE_ATTR);
+    }
+  else
+    {
+      set_error (error, context,
+                 G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed below <%s>"),
+                 element_name, "tag");
+    }
+}
+
+
+static gchar *
+get_tag_name (ParseInfo   *info,
+             const gchar *tag_name)
+{
+  gchar *name;
+  gint i;
+
+  name = g_strdup (tag_name);
+
+  if (!info->create_tags)
+    return name;
+
+  i = 0;
+
+  while (gtk_text_tag_table_lookup (info->buffer->tag_table, name) != NULL)
+    {
+      g_free (name);
+      name = g_strdup_printf ("%s-%d", tag_name, ++i);
+    }
+
+  if (i != 0)
+    {
+      g_hash_table_insert (info->substitutions, g_strdup (tag_name), g_strdup (name));
+    }
+
+  return name;
+}
+
+static void
+parse_tag_element (GMarkupParseContext  *context,
+                  const gchar          *element_name,
+                  const gchar         **attribute_names,
+                  const gchar         **attribute_values,
+                  ParseInfo            *info,
+                  GError              **error)
+{
+  const gchar *name, *priority;
+  gchar *tag_name;
+  gint id;
+  gint prio;
+  gchar *tmp;
+
+  g_assert (peek_state (info) == STATE_TAGS);
+
+  if (ELEMENT_IS ("tag"))
+    {
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values, TRUE, error,
+                             "priority", &priority, NULL))
+       return;
+
+      if (!check_id_or_name (context, element_name, attribute_names, attribute_values,
+                            &id, &name, error))
+       return;
+
+      if (name)
+       {
+         if (g_hash_table_lookup (info->defined_tags, name) != NULL)
+           {
+             set_error (error, context,
+                        G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                        _("Tag \"%s\" already defined"), name);
+             return;
+           }
+       }
+
+      prio = strtol (priority, &tmp, 10);
+
+      if (tmp == NULL || tmp == priority)
+       {
+         set_error (error, context,
+                    G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                    _("Tag \"%s\" has invalid priority \"%s\""), name, priority);
+         return;
+       }
+
+      if (name)
+       {
+         tag_name = get_tag_name (info, name);
+         info->current_tag = gtk_text_tag_new (tag_name);
+         g_free (tag_name);
+       }
+      else
+       {
+         info->current_tag = gtk_text_tag_new (NULL);
+         info->current_tag_id = id;
+       }
+
+      info->current_tag_prio = prio;
+
+      push_state (info, STATE_TAG);
+    }
+  else
+    {
+      set_error (error, context,
+                 G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed below <%s>"),
+                 element_name, "tags");
+    }
+}
+
+static void
+start_element_handler (GMarkupParseContext  *context,
+                      const gchar          *element_name,
+                      const gchar         **attribute_names,
+                      const gchar         **attribute_values,
+                      gpointer              user_data,
+                      GError              **error)
+{
+  ParseInfo *info = user_data;
+
+  switch (peek_state (info))
+    {
+    case STATE_START:
+      if (ELEMENT_IS ("text_view_markup"))
+       {
+         if (!check_no_attributes (context, element_name,
+                                   attribute_names, attribute_values, error))
+           return;
+
+         push_state (info, STATE_TEXT_VIEW_MARKUP);
+         break;
+       }
+      else
+        set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                   _("Outermost element in text must be <text_view_markup> not <%s>"),
+                   element_name);
+      break;
+    case STATE_TEXT_VIEW_MARKUP:
+      if (ELEMENT_IS ("tags"))
+       {
+         if (info->parsed_tags)
+           {
+             set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                        _("A <tags> element has already been specified"));
+             return;
+           }
+
+         if (!check_no_attributes (context, element_name,
+                                   attribute_names, attribute_values, error))
+           return;
+
+         push_state (info, STATE_TAGS);
+         break;
+       }
+      else if (ELEMENT_IS ("text"))
+       {
+         if (info->parsed_text)
+           {
+             set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                        _("A <text> element has already been specified"));
+             return;
+           }
+         else if (!info->parsed_tags)
+           {
+             set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                        _("A <text> element can't occur before a <tags> element"));
+             return;
+           }
+
+         if (!check_no_attributes (context, element_name,
+                                   attribute_names, attribute_values, error))
+           return;
+
+         push_state (info, STATE_TEXT);
+         break;
+       }
+      else
+       set_error (error, context,
+                  G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed below <%s>"),
+                  element_name, "text_view_markup");
+      break;
+    case STATE_TAGS:
+      parse_tag_element (context, element_name,
+                        attribute_names, attribute_values,
+                        info, error);
+      break;
+    case STATE_TAG:
+      parse_attr_element (context, element_name,
+                         attribute_names, attribute_values,
+                         info, error);
+      break;
+    case STATE_TEXT:
+    case STATE_APPLY_TAG:
+      parse_apply_tag_element (context, element_name,
+                              attribute_names, attribute_values,
+                              info, error);
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+}
+
+static gint
+sort_tag_prio (TextTagPrio *a,
+              TextTagPrio *b)
+{
+  if (a->prio < b->prio)
+    return -1;
+  else if (a->prio > b->prio)
+    return 1;
+  else
+    return 0;
+}
+
+static void
+end_element_handler (GMarkupParseContext  *context,
+                    const gchar          *element_name,
+                    gpointer              user_data,
+                    GError              **error)
+{
+  ParseInfo *info = user_data;
+  gchar *tmp;
+  GList *list;
+
+  switch (peek_state (info))
+    {
+    case STATE_TAGS:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_TEXT_VIEW_MARKUP);
+
+      info->parsed_tags = TRUE;
+
+      /* Sort list and add the tags */
+      info->tag_priorities = g_list_sort (info->tag_priorities,
+                                         (GCompareFunc)sort_tag_prio);
+      list = info->tag_priorities;
+      while (list)
+       {
+         TextTagPrio *prio = list->data;
+
+         if (info->create_tags)
+           gtk_text_tag_table_add (info->buffer->tag_table, prio->tag);
+
+         g_object_unref (prio->tag);
+         prio->tag = NULL;
+
+         list = list->next;
+       }
+
+      break;
+    case STATE_TAG:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_TAGS);
+
+      if (info->current_tag->name)
+       {
+         /* Add tag to defined tags hash */
+         tmp = g_strdup (info->current_tag->name);
+         g_hash_table_insert (info->defined_tags,
+                              tmp, tmp);
+       }
+      else
+       {
+         g_hash_table_insert (info->anonymous_tags,
+                              GINT_TO_POINTER (info->current_tag_id),
+                              info->current_tag);
+       }
+
+      if (info->create_tags)
+       {
+         TextTagPrio *prio;
+
+         /* add the tag to the list */
+         prio = g_new0 (TextTagPrio, 1);
+         prio->prio = info->current_tag_prio;
+         prio->tag = info->current_tag;
+
+         info->tag_priorities = g_list_prepend (info->tag_priorities, prio);
+       }
+
+      info->current_tag = NULL;
+      break;
+    case STATE_ATTR:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_TAG);
+      break;
+    case STATE_APPLY_TAG:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_APPLY_TAG ||
+               peek_state (info) == STATE_TEXT);
+
+      /* Pop tag */
+      info->tag_stack = g_slist_delete_link (info->tag_stack,
+                                            info->tag_stack);
+
+      break;
+    case STATE_TEXT:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_TEXT_VIEW_MARKUP);
+
+      info->spans = g_list_reverse (info->spans);
+      info->parsed_text = TRUE;
+      break;
+    case STATE_TEXT_VIEW_MARKUP:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_START);
+      break;
+    case STATE_PIXBUF:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_APPLY_TAG ||
+               peek_state (info) == STATE_TEXT);
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+}
+
+static gboolean
+all_whitespace (const char *text,
+                int         text_len)
+{
+  const char *p;
+  const char *end;
+
+  p = text;
+  end = text + text_len;
+
+  while (p != end)
+    {
+      if (!g_ascii_isspace (*p))
+        return FALSE;
+
+      p = g_utf8_next_char (p);
+    }
+
+  return TRUE;
+}
+
+static void
+text_handler (GMarkupParseContext  *context,
+             const gchar          *text,
+             gsize                 text_len,
+             gpointer              user_data,
+             GError              **error)
+{
+  ParseInfo *info = user_data;
+  TextSpan *span;
+
+  if (all_whitespace (text, text_len) &&
+      peek_state (info) != STATE_TEXT &&
+      peek_state (info) != STATE_APPLY_TAG)
+    return;
+
+  switch (peek_state (info))
+    {
+    case STATE_START:
+      g_assert_not_reached (); /* gmarkup shouldn't do this */
+      break;
+    case STATE_TEXT:
+    case STATE_APPLY_TAG:
+      if (text_len == 0)
+       return;
+
+      span = g_new0 (TextSpan, 1);
+      span->text = g_strndup (text, text_len);
+      span->tags = g_slist_copy (info->tag_stack);
+
+      info->spans = g_list_prepend (info->spans, span);
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+}
+
+static void
+parse_info_init (ParseInfo     *info,
+                GtkTextBuffer *buffer,
+                gboolean       create_tags,
+                GList         *headers)
+{
+  info->states = g_slist_prepend (NULL, GINT_TO_POINTER (STATE_START));
+
+  info->create_tags = create_tags;
+  info->headers = headers;
+  info->defined_tags = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+  info->substitutions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+  info->anonymous_tags = g_hash_table_new_full (NULL, NULL, NULL, NULL);
+  info->tag_stack = NULL;
+  info->spans = NULL;
+  info->parsed_text = FALSE;
+  info->parsed_tags = FALSE;
+  info->current_tag = NULL;
+  info->current_tag_prio = -1;
+  info->tag_priorities = NULL;
+
+  info->buffer = buffer;
+}
+
+static void
+text_span_free (TextSpan *span)
+{
+  g_free (span->text);
+  g_slist_free (span->tags);
+  g_free (span);
+}
+
+static void
+parse_info_free (ParseInfo *info)
+{
+  GSList *slist;
+  GList *list;
+
+  slist = info->tag_stack;
+  while (slist)
+    {
+      g_free (slist->data);
+
+      slist = slist->next;
+    }
+
+  g_slist_free (info->tag_stack);
+  g_slist_free (info->states);
+
+  g_hash_table_destroy (info->substitutions);
+  g_hash_table_destroy (info->defined_tags);
+
+  if (info->current_tag)
+    g_object_unref (info->current_tag);
+
+  list = info->spans;
+  while (list)
+    {
+      text_span_free (list->data);
+
+      list = list->next;
+    }
+  g_list_free (info->spans);
+
+  list = info->tag_priorities;
+  while (list)
+    {
+      TextTagPrio *prio = list->data;
+
+      if (prio->tag)
+       g_object_unref (prio->tag);
+      g_free (prio);
+
+      list = list->next;
+    }
+  g_list_free (info->tag_priorities);
+
+}
+
+static void
+insert_text (ParseInfo   *info,
+            GtkTextIter *iter)
+{
+  GtkTextIter start_iter;
+  GtkTextMark *mark;
+  GList *tmp;
+  GSList *tags;
+
+  start_iter = *iter;
+
+  mark = gtk_text_buffer_create_mark (info->buffer, "deserialize_insert_point",
+                                     &start_iter, TRUE);
+
+  tmp = info->spans;
+  while (tmp)
+    {
+      TextSpan *span = tmp->data;
+
+      if (span->text)
+       gtk_text_buffer_insert (info->buffer, iter, span->text, -1);
+      else
+       {
+         gtk_text_buffer_insert_pixbuf (info->buffer, iter, span->pixbuf);
+         g_object_unref (span->pixbuf);
+       }
+      gtk_text_buffer_get_iter_at_mark (info->buffer, &start_iter, mark);
+
+      /* Apply tags */
+      tags = span->tags;
+      while (tags)
+       {
+         GtkTextTag *tag = tags->data;
+
+         gtk_text_buffer_apply_tag (info->buffer, tag,
+                                    &start_iter, iter);
+
+         tags = tags->next;
+       }
+
+      gtk_text_buffer_move_mark (info->buffer, mark, iter);
+
+      tmp = tmp->next;
+    }
+
+  gtk_text_buffer_delete_mark (info->buffer, mark);
+}
+
+
+
+static int
+read_int (const guchar *start)
+{
+  int result;
+
+  result =
+    start[0] << 24 |
+    start[1] << 16 |
+    start[2] << 8 |
+    start[3];
+
+  return result;
+}
+
+static gboolean
+header_is (Header      *header,
+           const gchar *id)
+{
+  return (strncmp (header->id, id, strlen (id)) == 0);
+}
+
+static GList *
+read_headers (const gchar *start,
+             gint         len,
+             GError     **error)
+{
+  int i = 0;
+  int section_len;
+  Header *header;
+  GList *headers = NULL;
+
+  while (i < len)
+    {
+      if (i + 30 >= len)
+       goto error;
+
+      if (strncmp (start + i, "GTKTEXTBUFFERCONTENTS-0001", 26) == 0 ||
+         strncmp (start + i, "GTKTEXTBUFFERPIXBDATA-0001", 26) == 0)
+       {
+         section_len = read_int ((const guchar *) start + i + 26);
+
+         if (i + 30 + section_len > len)
+           goto error;
+
+         header = g_new0 (Header, 1);
+         header->id = start + i;
+         header->length = section_len;
+         header->start = start + i + 30;
+
+         i += 30 + section_len;
+
+         headers = g_list_prepend (headers, header);
+       }
+      else
+       break;
+    }
+
+  return g_list_reverse (headers);
+
+ error:
+  g_list_foreach (headers, (GFunc) g_free, NULL);
+  g_list_free (headers);
+
+  g_set_error (error,
+              G_MARKUP_ERROR,
+              G_MARKUP_ERROR_PARSE,
+              _("Serialized data is malformed"));
+
+  return NULL;
+}
+
+static gboolean
+deserialize_text (GtkTextBuffer *buffer,
+                 GtkTextIter   *iter,
+                 const gchar   *text,
+                 gint           len,
+                 gboolean       create_tags,
+                 GError       **error,
+                 GList         *headers)
+{
+  GMarkupParseContext *context;
+  ParseInfo info;
+  gboolean retval = FALSE;
+
+
+  static GMarkupParser rich_text_parser = {
+    start_element_handler,
+    end_element_handler,
+    text_handler,
+    NULL,
+    NULL
+  };
+
+  parse_info_init (&info, buffer, create_tags, headers);
+
+  context = g_markup_parse_context_new (&rich_text_parser,
+                                        0, &info, NULL);
+
+  if (!g_markup_parse_context_parse (context,
+                                     text,
+                                     len,
+                                     error))
+    goto out;
+
+  if (!g_markup_parse_context_end_parse (context, error))
+    goto out;
+
+  retval = TRUE;
+
+  /* Now insert the text */
+  insert_text (&info, iter);
+
+ out:
+  parse_info_free (&info);
+
+  g_markup_parse_context_free (context);
+
+  return retval;
+}
+
+gboolean
+_gtk_text_buffer_deserialize_rich_text (GtkTextBuffer *register_buffer,
+                                        GtkTextBuffer *content_buffer,
+                                        GtkTextIter   *iter,
+                                        const guint8  *text,
+                                        gsize          length,
+                                        gboolean       create_tags,
+                                        gpointer       user_data,
+                                        GError       **error)
+{
+  GList *headers;
+  Header *header;
+  gboolean retval;
+
+  headers = read_headers ((gchar *) text, length, error);
+
+  if (!headers)
+    return FALSE;
+
+  header = headers->data;
+  if (!header_is (header, "GTKTEXTBUFFERCONTENTS-0001"))
+    {
+      g_set_error (error,
+                  G_MARKUP_ERROR,
+                  G_MARKUP_ERROR_PARSE,
+                  _("Serialized data is malformed. First section isn't GTKTEXTBUFFERCONTENTS-0001"));
+
+      retval = FALSE;
+      goto out;
+    }
+
+  retval = deserialize_text (content_buffer, iter,
+                            header->start, header->length,
+                            create_tags, error, headers->next);
+
+ out:
+  g_list_foreach (headers, (GFunc)g_free, NULL);
+  g_list_free (headers);
+
+  return retval;
+}
diff --git a/gtk/gtktextbufferserialize.h b/gtk/gtktextbufferserialize.h
new file mode 100644 (file)
index 0000000..63a749e
--- /dev/null
@@ -0,0 +1,43 @@
+/* gtktextbufferserialize.h
+ *
+ * Copyright (C) 2004 Nokia Corporation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GTK_TEXT_BUFFER_SERIALIZE_H__
+#define __GTK_TEXT_BUFFER_SERIALIZE_H__
+
+#include <gtk/gtktextbuffer.h>
+
+guint8 * _gtk_text_buffer_serialize_rich_text   (GtkTextBuffer     *register_buffer,
+                                                 GtkTextBuffer     *content_buffer,
+                                                 const GtkTextIter *start,
+                                                 const GtkTextIter *end,
+                                                 gsize             *length,
+                                                 gpointer           user_data);
+
+gboolean _gtk_text_buffer_deserialize_rich_text (GtkTextBuffer     *register_buffer,
+                                                 GtkTextBuffer     *content_buffer,
+                                                 GtkTextIter       *iter,
+                                                 const guint8      *data,
+                                                 gsize              length,
+                                                 gboolean           create_tags,
+                                                 gpointer           user_data,
+                                                 GError           **error);
+
+
+#endif /* __GTK_TEXT_BUFFER_SERIALIZE_H__ */
index 6678e4e3b61c20fc12815615ffd58fc41a1165ea..11758778d70f034c0f8184b075a15fdf504245ee 100644 (file)
 
 #include <config.h>
 #include "gtktextutil.h"
-#include "gtkintl.h"
+#include "gtktextview.h"
+
+#define GTK_TEXT_USE_INTERNAL_UNSUPPORTED_API
+
+#include "gtktextdisplay.h"
+#include "gtktextbuffer.h"
 #include "gtkmenuitem.h"
+#include "gtkintl.h"
 #include "gtkalias.h"
 
 #define DRAG_ICON_MAX_WIDTH 250
-#define DRAG_ICON_LAYOUT_BORDER 2
+#define DRAG_ICON_MAX_HEIGHT 250
+#define DRAG_ICON_LAYOUT_BORDER 5
 #define DRAG_ICON_MAX_LINES 7
 #define ELLIPSIS_CHARACTER "\xe2\x80\xa6"
 
@@ -177,7 +184,7 @@ limit_layout_lines (PangoLayout *layout)
  *
  * Creates a drag and drop icon from @text.
  **/
-GdkPixmap*
+GdkPixmap *
 _gtk_text_util_create_drag_icon (GtkWidget *widget, 
                                  gchar     *text,
                                  gsize      len)
@@ -238,3 +245,124 @@ _gtk_text_util_create_drag_icon (GtkWidget *widget,
 
   return drawable;
 }
+
+static void
+gtk_text_view_set_attributes_from_style (GtkTextView        *text_view,
+                                         GtkTextAttributes  *values,
+                                         GtkStyle           *style)
+{
+  values->appearance.bg_color = style->base[GTK_STATE_NORMAL];
+  values->appearance.fg_color = style->text[GTK_STATE_NORMAL];
+
+  if (values->font)
+    pango_font_description_free (values->font);
+
+  values->font = pango_font_description_copy (style->font_desc);
+}
+
+GdkPixmap *
+_gtk_text_util_create_rich_drag_icon (GtkWidget     *widget,
+                                      GtkTextBuffer *buffer,
+                                      GtkTextIter   *start,
+                                      GtkTextIter   *end)
+{
+  GdkDrawable       *drawable = NULL;
+  gint               pixmap_height, pixmap_width;
+  gint               layout_width, layout_height;
+  GtkTextBuffer     *new_buffer;
+  GtkTextLayout     *layout;
+  GtkTextAttributes *style;
+  PangoContext      *ltr_context, *rtl_context;
+  GtkTextIter        iter;
+
+   g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
+   g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
+   g_return_val_if_fail (start != NULL, NULL);
+   g_return_val_if_fail (end != NULL, NULL);
+
+   new_buffer = gtk_text_buffer_new (gtk_text_buffer_get_tag_table (buffer));
+   gtk_text_buffer_get_start_iter (new_buffer, &iter);
+
+   gtk_text_buffer_insert_range (new_buffer, &iter, start, end);
+
+   gtk_text_buffer_get_start_iter (new_buffer, &iter);
+
+   layout = gtk_text_layout_new ();
+
+   ltr_context = gtk_widget_create_pango_context (widget);
+   pango_context_set_base_dir (ltr_context, PANGO_DIRECTION_LTR);
+   rtl_context = gtk_widget_create_pango_context (widget);
+   pango_context_set_base_dir (rtl_context, PANGO_DIRECTION_RTL);
+
+   gtk_text_layout_set_contexts (layout, ltr_context, rtl_context);
+
+   g_object_unref (ltr_context);
+   g_object_unref (rtl_context);
+
+   style = gtk_text_attributes_new ();
+
+   layout_width = widget->allocation.width;
+
+   if (GTK_IS_TEXT_VIEW (widget))
+     {
+       gtk_widget_ensure_style (widget);
+       gtk_text_view_set_attributes_from_style (GTK_TEXT_VIEW (widget),
+                                                style, widget->style);
+
+       layout_width = layout_width
+         - gtk_text_view_get_border_window_size (GTK_TEXT_VIEW (widget), GTK_TEXT_WINDOW_LEFT)
+         - gtk_text_view_get_border_window_size (GTK_TEXT_VIEW (widget), GTK_TEXT_WINDOW_RIGHT);
+     }
+
+   style->direction = gtk_widget_get_direction (widget);
+   style->wrap_mode = PANGO_WRAP_WORD_CHAR;
+
+   gtk_text_layout_set_default_style (layout, style);
+   gtk_text_attributes_unref (style);
+
+   gtk_text_layout_set_buffer (layout, new_buffer);
+   gtk_text_layout_set_cursor_visible (layout, FALSE);
+   gtk_text_layout_set_screen_width (layout, layout_width);
+
+   gtk_text_layout_validate (layout, DRAG_ICON_MAX_HEIGHT);
+   gtk_text_layout_get_size (layout, &layout_width, &layout_height);
+
+   g_print ("%s: layout size %d %d\n", G_STRFUNC, layout_width, layout_height);
+
+   layout_width = MIN (layout_width, DRAG_ICON_MAX_WIDTH);
+   layout_height = MIN (layout_height, DRAG_ICON_MAX_HEIGHT);
+
+   pixmap_width  = layout_width + DRAG_ICON_LAYOUT_BORDER * 2;
+   pixmap_height = layout_height + DRAG_ICON_LAYOUT_BORDER * 2;
+
+   g_print ("%s: pixmap size %d %d\n", G_STRFUNC, pixmap_width, pixmap_height);
+
+   drawable = gdk_pixmap_new (widget->window,
+                              pixmap_width  + 2, pixmap_height + 2, -1);
+
+   gdk_draw_rectangle (drawable,
+                       widget->style->base_gc [GTK_WIDGET_STATE (widget)],
+                       TRUE,
+                       0, 0,
+                       pixmap_width + 1,
+                       pixmap_height + 1);
+
+   gtk_text_layout_draw (layout, widget, drawable,
+                         widget->style->text_gc [GTK_WIDGET_STATE (widget)],
+                         - (1 + DRAG_ICON_LAYOUT_BORDER),
+                         - (1 + DRAG_ICON_LAYOUT_BORDER),
+                         0, 0,
+                         pixmap_width, pixmap_height, NULL);
+
+   gdk_draw_rectangle (drawable,
+                       widget->style->black_gc,
+                       FALSE,
+                       0, 0,
+                       pixmap_width + 1,
+                       pixmap_height + 1);
+
+   g_object_unref (layout);
+   g_object_unref (new_buffer);
+
+   return drawable;
+}
index 27b900e4355ff98fbf5937400edf411baeb7858f..e69a925af1ad095d9a24f383dac35bcd15cbea44 100644 (file)
@@ -31,6 +31,7 @@
 #include <gtk/gtkwidget.h>
 #include <gtk/gtkmenushell.h>
 #include <gtk/gtkeditable.h>
+#include <gtk/gtktextbuffer.h>
 
 
 G_BEGIN_DECLS
@@ -47,8 +48,12 @@ void _gtk_text_util_append_special_char_menuitems (GtkMenuShell              *me
 
 G_END_DECLS
 
-GdkPixmap* _gtk_text_util_create_drag_icon  (GtkWidget *widget, 
-                                            gchar     *text,
-                                            gsize      len);
+GdkPixmap* _gtk_text_util_create_drag_icon      (GtkWidget     *widget,
+                                                 gchar         *text,
+                                                 gsize          len);
+GdkPixmap* _gtk_text_util_create_rich_drag_icon (GtkWidget     *widget,
+                                                 GtkTextBuffer *buffer,
+                                                 GtkTextIter   *start,
+                                                 GtkTextIter   *end);
 
 #endif /* __GTK_TEXT_UTIL_H__ */
index 8b4bec751234e1ecdc7466821275fb7a5a9d372b..3049fae6dcba1e1110b7305d96877818b8291b2e 100644 (file)
@@ -39,6 +39,7 @@
 #include "gtkseparatormenuitem.h"
 #include "gtksettings.h"
 #include "gtkstock.h"
+#include "gtktextbufferrichtext.h"
 #include "gtktextdisplay.h"
 #include "gtktextview.h"
 #include "gtkimmulticontext.h"
@@ -314,6 +315,9 @@ static void gtk_text_view_mark_set_handler       (GtkTextBuffer     *buffer,
                                                   const GtkTextIter *location,
                                                   GtkTextMark       *mark,
                                                   gpointer           data);
+static void gtk_text_view_target_list_notify     (GtkTextBuffer     *buffer,
+                                                  const GParamSpec  *pspec,
+                                                  gpointer           data);
 static void gtk_text_view_get_cursor_location    (GtkTextView       *text_view,
                                                  GdkRectangle      *pos);
 static void gtk_text_view_get_virtual_cursor_pos (GtkTextView       *text_view,
@@ -414,10 +418,6 @@ static gint           text_window_get_width       (GtkTextWindow     *win);
 static gint           text_window_get_height      (GtkTextWindow     *win);
 
 
-static const GtkTargetEntry target_table[] = {
-  { "GTK_TEXT_BUFFER_CONTENTS", GTK_TARGET_SAME_APP, 0 },
-};
-
 static GtkContainerClass *parent_class = NULL;
 static guint signals[LAST_SIGNAL] = { 0 };
 
@@ -1062,11 +1062,8 @@ gtk_text_view_init (GtkTextView *text_view)
   text_view->tabs = NULL;
   text_view->editable = TRUE;
 
-  gtk_drag_dest_set (widget,
-                    0,
-                     target_table, G_N_ELEMENTS (target_table),
+  gtk_drag_dest_set (widget, 0, NULL, 0,
                      GDK_ACTION_COPY | GDK_ACTION_MOVE);
-  gtk_drag_dest_add_text_targets (widget);
 
   text_view->virtual_cursor_x = -1;
   text_view->virtual_cursor_y = -1;
@@ -1190,6 +1187,9 @@ gtk_text_view_set_buffer (GtkTextView   *text_view,
       g_signal_handlers_disconnect_by_func (text_view->buffer,
                                            gtk_text_view_mark_set_handler,
                                            text_view);
+      g_signal_handlers_disconnect_by_func (text_view->buffer,
+                                            gtk_text_view_target_list_notify,
+                                            text_view);
       g_object_unref (text_view->buffer);
       text_view->dnd_mark = NULL;
 
@@ -1225,7 +1225,13 @@ gtk_text_view_set_buffer (GtkTextView   *text_view,
       text_view->first_para_pixels = 0;
 
       g_signal_connect (text_view->buffer, "mark_set",
-                       G_CALLBACK (gtk_text_view_mark_set_handler), text_view);
+                       G_CALLBACK (gtk_text_view_mark_set_handler),
+                        text_view);
+      g_signal_connect (text_view->buffer, "notify::paste-target-list",
+                       G_CALLBACK (gtk_text_view_target_list_notify),
+                        text_view);
+
+      gtk_text_view_target_list_notify (text_view->buffer, NULL, text_view);
 
       if (GTK_WIDGET_REALIZED (text_view))
        {
@@ -5984,21 +5990,6 @@ gtk_text_view_reset_im_context (GtkTextView *text_view)
     }
 }
 
-static gchar*
-_gtk_text_view_get_selected_text (GtkTextView *text_view)
-{
-  GtkTextBuffer *buffer;
-  GtkTextIter    start, end;
-  gchar         *text = NULL;
-
-  buffer = gtk_text_view_get_buffer (text_view);
-
-  if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
-    text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
-
-  return text;
-}
-
 /*
  * DND feature
  */
@@ -6008,29 +5999,30 @@ drag_begin_cb (GtkWidget      *widget,
                GdkDragContext *context,
                gpointer        data)
 {
-  GtkTextView *text_view;
-  gchar *text;
-  GdkPixmap *pixmap = NULL;
+  GtkTextView   *text_view = GTK_TEXT_VIEW (widget);
+  GtkTextBuffer *buffer = gtk_text_view_get_buffer (text_view);
+  GtkTextIter    start;
+  GtkTextIter    end;
+  GdkPixmap     *pixmap = NULL;
 
   g_signal_handlers_disconnect_by_func (widget, drag_begin_cb, NULL);
 
-  text_view = GTK_TEXT_VIEW (widget);
-
-  text   = _gtk_text_view_get_selected_text (text_view);
-  pixmap = _gtk_text_util_create_drag_icon (widget, text, -1);
+  if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
+    pixmap = _gtk_text_util_create_rich_drag_icon (widget, buffer, &start, &end);
 
   if (pixmap)
-    gtk_drag_set_icon_pixmap (context,
-                              gdk_drawable_get_colormap (pixmap),
-                              pixmap,
-                              NULL,
-                              -2, -2);
+    {
+      gtk_drag_set_icon_pixmap (context,
+                                gdk_drawable_get_colormap (pixmap),
+                                pixmap,
+                                NULL,
+                                -2, -2);
+      g_object_unref (pixmap);
+    }
   else
-    gtk_drag_set_icon_default (context);
-  
-  if (pixmap)
-    g_object_unref (pixmap);
-  g_free (text);
+    {
+      gtk_drag_set_icon_default (context);
+    }
 }
 
 static void
@@ -6038,23 +6030,19 @@ gtk_text_view_start_selection_dnd (GtkTextView       *text_view,
                                    const GtkTextIter *iter,
                                    GdkEventMotion    *event)
 {
-  GtkTargetList  *target_list;
+  GtkTargetList *target_list;
 
   text_view->drag_start_x = -1;
   text_view->drag_start_y = -1;
   text_view->pending_place_cursor_button = 0;
-  
-  target_list = gtk_target_list_new (target_table,
-                                     G_N_ELEMENTS (target_table));
-  gtk_target_list_add_text_targets (target_list, 0);
 
-  g_signal_connect (text_view, "drag-begin", 
+  target_list = gtk_text_buffer_get_copy_target_list (get_buffer (text_view));
+
+  g_signal_connect (text_view, "drag-begin",
                     G_CALLBACK (drag_begin_cb), NULL);
   gtk_drag_begin (GTK_WIDGET (text_view), target_list,
                  GDK_ACTION_COPY | GDK_ACTION_MOVE,
                  1, (GdkEvent*)event);
-
-  gtk_target_list_unref (target_list);
 }
 
 static void
@@ -6077,30 +6065,49 @@ gtk_text_view_drag_data_get (GtkWidget        *widget,
                              guint             info,
                              guint             time)
 {
-  GtkTextView *text_view;
-
-  text_view = GTK_TEXT_VIEW (widget);
+  GtkTextView *text_view = GTK_TEXT_VIEW (widget);
+  GtkTextBuffer *buffer = gtk_text_view_get_buffer (text_view);
 
-  if (selection_data->target == gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"))
+  if (info == GTK_TEXT_BUFFER_TARGET_INFO_BUFFER_CONTENTS)
     {
-      GtkTextBuffer *buffer = gtk_text_view_get_buffer (text_view);
-
       gtk_selection_data_set (selection_data,
                               gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"),
                               8, /* bytes */
                               (void*)&buffer,
                               sizeof (buffer));
     }
-  else
+  else if (info == GTK_TEXT_BUFFER_TARGET_INFO_RICH_TEXT)
     {
-      gchar *str;
       GtkTextIter start;
       GtkTextIter end;
+      guint8 *str = NULL;
+      gsize len;
+
+      if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
+        {
+          /* Extract the selected text */
+          str = gtk_text_buffer_serialize (buffer, buffer,
+                                           selection_data->target,
+                                           &start, &end,
+                                           &len);
+        }
 
-      str = NULL;
+      if (str)
+        {
+          gtk_selection_data_set (selection_data,
+                                  selection_data->target,
+                                  8, /* bytes */
+                                  (guchar *) str, len);
+          g_free (str);
+        }
+    }
+  else
+    {
+      GtkTextIter start;
+      GtkTextIter end;
+      gchar *str = NULL;
 
-      if (gtk_text_buffer_get_selection_bounds (get_buffer (text_view),
-                                                &start, &end))
+      if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
         {
           /* Extract the selected text */
           str = gtk_text_iter_get_visible_text (&start, &end);
@@ -6179,7 +6186,7 @@ gtk_text_view_drag_motion (GtkWidget        *widget,
       /* can't accept any of the offered targets */
     }                                 
   else if (gtk_text_buffer_get_selection_bounds (get_buffer (text_view),
-                                            &start, &end) &&
+                                                 &start, &end) &&
            gtk_text_iter_compare (&newplace, &start) >= 0 &&
            gtk_text_iter_compare (&newplace, &end) <= 0)
     {
@@ -6284,14 +6291,14 @@ insert_text_data (GtkTextView      *text_view,
                   GtkTextIter      *drop_point,
                   GtkSelectionData *selection_data)
 {
-  gchar *str;
+  guchar *str;
 
   str = gtk_selection_data_get_text (selection_data);
 
   if (str)
     {
       gtk_text_buffer_insert_interactive (get_buffer (text_view),
-                                          drop_point, str, -1,
+                                          drop_point, (gchar *) str, -1,
                                           text_view->editable);
       g_free (str);
     }
@@ -6329,7 +6336,7 @@ gtk_text_view_drag_data_received (GtkWidget        *widget,
 
   gtk_text_buffer_begin_user_action (buffer);
 
-  if (selection_data->target == gdk_atom_intern_static_string ("GTK_TEXT_BUFFER_CONTENTS"))
+  if (info == GTK_TEXT_BUFFER_TARGET_INFO_BUFFER_CONTENTS)
     {
       GtkTextBuffer *src_buffer = NULL;
       GtkTextIter start, end;
@@ -6347,7 +6354,38 @@ gtk_text_view_drag_data_received (GtkWidget        *widget,
 
       if (gtk_text_buffer_get_tag_table (src_buffer) !=
           gtk_text_buffer_get_tag_table (buffer))
-        copy_tags = FALSE;
+        {
+          /*  try to find a suitable rich text target instead  */
+          GdkAtom *atoms;
+          gint     n_atoms;
+          GList   *list;
+          GdkAtom  target = GDK_NONE;
+
+          copy_tags = FALSE;
+
+          atoms = gtk_text_buffer_get_deserialize_formats (buffer, &n_atoms);
+
+          for (list = context->targets; list; list = g_list_next (list))
+            {
+              gint i;
+
+              for (i = 0; i < n_atoms; i++)
+                if (GUINT_TO_POINTER (atoms[i]) == list->data)
+                  {
+                    target = atoms[i];
+                    break;
+                  }
+            }
+
+          g_free (atoms);
+
+          if (target != GDK_NONE)
+            {
+              gtk_drag_get_data (widget, context, target, time);
+              gtk_text_buffer_end_user_action (buffer);
+              return;
+            }
+        }
 
       if (gtk_text_buffer_get_selection_bounds (src_buffer,
                                                 &start,
@@ -6371,9 +6409,28 @@ gtk_text_view_drag_data_received (GtkWidget        *widget,
             }
         }
     }
+  else if (selection_data->length > 0 &&
+           info == GTK_TEXT_BUFFER_TARGET_INFO_RICH_TEXT)
+    {
+      gboolean retval;
+      GError *error = NULL;
+
+      retval = gtk_text_buffer_deserialize (buffer, buffer,
+                                            selection_data->target,
+                                            &drop_point,
+                                            (guint8 *) selection_data->data,
+                                            selection_data->length,
+                                            &error);
+
+      if (!retval)
+        {
+          g_warning ("error pasting: %s\n", error->message);
+          g_clear_error (&error);
+        }
+    }
   else
     insert_text_data (text_view, &drop_point, selection_data);
+
  done:
   gtk_drag_finish (context, success,
                   success && context->action == GDK_ACTION_MOVE,
@@ -6825,6 +6882,14 @@ gtk_text_view_mark_set_handler (GtkTextBuffer     *buffer,
     gtk_text_view_reset_im_context (text_view);
 }
 
+static void
+gtk_text_view_target_list_notify (GtkTextBuffer    *buffer,
+                                  const GParamSpec *pspec,
+                                  gpointer          data)
+{
+  gtk_drag_dest_set_target_list (data, gtk_text_buffer_get_paste_target_list (buffer));
+}
+
 static void
 gtk_text_view_get_cursor_location  (GtkTextView   *text_view,
                                    GdkRectangle  *pos)
index 05cf1f0454ec68f9aa4d0e9fff89ba50bfe04537..27765e165f2a0984805a77e18e5db5e8cf8171e5 100644 (file)
@@ -1282,6 +1282,267 @@ do_properties (gpointer callback_data,
   create_prop_editor (G_OBJECT (view->text_view), 0);
 }
 
+static void
+rich_text_store_populate (GtkListStore  *store,
+                          GtkTextBuffer *buffer,
+                          gboolean       deserialize)
+{
+  GdkAtom *formats;
+  gint     n_formats;
+  gint     i;
+
+  gtk_list_store_clear (store);
+
+  if (deserialize)
+    formats = gtk_text_buffer_get_deserialize_formats (buffer, &n_formats);
+  else
+    formats = gtk_text_buffer_get_serialize_formats (buffer, &n_formats);
+
+  for (i = 0; i < n_formats; i++)
+    {
+      GtkTreeIter  iter;
+      gchar       *mime_type;
+      gboolean     can_create_tags = FALSE;
+
+      mime_type = gdk_atom_name (formats[i]);
+
+      if (deserialize)
+        can_create_tags =
+          gtk_text_buffer_deserialize_get_can_create_tags (buffer, formats[i]);
+
+      gtk_list_store_append (store, &iter);
+      gtk_list_store_set (store, &iter,
+                          0, formats[i],
+                          1, mime_type,
+                          2, can_create_tags,
+                          -1);
+
+      g_free (mime_type);
+    }
+
+  g_free (formats);
+}
+
+static void
+rich_text_paste_target_list_notify (GtkTextBuffer    *buffer,
+                                    const GParamSpec *pspec,
+                                    GtkListStore     *store)
+{
+  rich_text_store_populate (store, buffer, TRUE);
+}
+
+static void
+rich_text_copy_target_list_notify (GtkTextBuffer    *buffer,
+                                   const GParamSpec *pspec,
+                                   GtkListStore     *store)
+{
+  rich_text_store_populate (store, buffer, FALSE);
+}
+
+static void
+rich_text_can_create_tags_toggled (GtkCellRendererToggle *toggle,
+                                   const gchar           *path,
+                                   GtkTreeModel          *model)
+{
+  GtkTreeIter iter;
+
+  if (gtk_tree_model_get_iter_from_string (model, &iter, path))
+    {
+      GtkTextBuffer *buffer;
+      GdkAtom        format;
+      gboolean       can_create_tags;
+
+      buffer = g_object_get_data (G_OBJECT (model), "buffer");
+
+      gtk_tree_model_get (model, &iter,
+                          0, &format,
+                          2, &can_create_tags,
+                          -1);
+
+      gtk_text_buffer_deserialize_set_can_create_tags (buffer, format,
+                                                       !can_create_tags);
+
+      gtk_list_store_set (GTK_LIST_STORE (model), &iter,
+                          2, !can_create_tags,
+                          -1);
+    }
+}
+
+static void
+rich_text_unregister_clicked (GtkWidget   *button,
+                              GtkTreeView *tv)
+{
+  GtkTreeSelection *sel = gtk_tree_view_get_selection (tv);
+  GtkTreeModel     *model;
+  GtkTreeIter       iter;
+
+  if (gtk_tree_selection_get_selected (sel, &model, &iter))
+    {
+      GtkTextBuffer *buffer;
+      gboolean       deserialize;
+      GdkAtom        format;
+
+      buffer = g_object_get_data (G_OBJECT (model), "buffer");
+      deserialize = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (model),
+                                                        "deserialize"));
+
+      gtk_tree_model_get (model, &iter,
+                          0, &format,
+                          -1);
+
+      if (deserialize)
+        gtk_text_buffer_unregister_deserialize_format (buffer, format);
+      else
+        gtk_text_buffer_unregister_serialize_format (buffer, format);
+    }
+}
+
+static void
+rich_text_register_clicked (GtkWidget   *button,
+                            GtkTreeView *tv)
+{
+  GtkWidget *dialog;
+  GtkWidget *label;
+  GtkWidget *entry;
+
+  dialog = gtk_dialog_new_with_buttons ("Register new Tagset",
+                                        GTK_WINDOW (gtk_widget_get_toplevel (button)),
+                                        GTK_DIALOG_DESTROY_WITH_PARENT,
+                                        GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                                        GTK_STOCK_OK,     GTK_RESPONSE_OK,
+                                        NULL);
+  label = gtk_label_new ("Enter tagset name or leave blank for "
+                         "unrestricted internal format:");
+  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), label,
+                      FALSE, FALSE, 0);
+
+  entry = gtk_entry_new ();
+  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), entry,
+                      FALSE, FALSE, 0);
+
+  gtk_widget_show_all (dialog);
+
+  if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK)
+    {
+      GtkTreeModel  *model  = gtk_tree_view_get_model (tv);
+      GtkTextBuffer *buffer = g_object_get_data (G_OBJECT (model), "buffer");
+      const gchar   *tagset = gtk_entry_get_text (GTK_ENTRY (entry));
+      gboolean       deserialize;
+
+      deserialize = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (model),
+                                                        "deserialize"));
+
+      if (tagset && ! strlen (tagset))
+        tagset = NULL;
+
+      if (deserialize)
+        gtk_text_buffer_register_deserialize_tagset (buffer, tagset);
+      else
+        gtk_text_buffer_register_serialize_tagset (buffer, tagset);
+    }
+
+  gtk_widget_destroy (dialog);
+}
+
+static void
+do_rich_text (gpointer callback_data,
+              guint deserialize,
+              GtkWidget *widget)
+{
+  View *view = view_from_widget (widget);
+  GtkTextBuffer *buffer;
+  GtkWidget *dialog;
+  GtkWidget *tv;
+  GtkWidget *sw;
+  GtkWidget *hbox;
+  GtkWidget *button;
+  GtkListStore *store;
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view->text_view));
+
+  dialog = gtk_dialog_new_with_buttons (deserialize ?
+                                        "Rich Text Paste & Drop" :
+                                        "Rich Text Copy & Drag",
+                                        GTK_WINDOW (view->window),
+                                        GTK_DIALOG_DESTROY_WITH_PARENT,
+                                        GTK_STOCK_CLOSE, 0,
+                                        NULL);
+  g_signal_connect (dialog, "response",
+                    G_CALLBACK (gtk_widget_destroy),
+                    NULL);
+
+  store = gtk_list_store_new (3,
+                              G_TYPE_POINTER,
+                              G_TYPE_STRING,
+                              G_TYPE_BOOLEAN);
+
+  g_object_set_data (G_OBJECT (store), "buffer", buffer);
+  g_object_set_data (G_OBJECT (store), "deserialize",
+                     GUINT_TO_POINTER (deserialize));
+
+  tv = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
+  g_object_unref (store);
+
+  gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tv),
+                                               0, "Rich Text Format",
+                                               gtk_cell_renderer_text_new (),
+                                               "text", 1,
+                                               NULL);
+
+  if (deserialize)
+    {
+      GtkCellRenderer *renderer = gtk_cell_renderer_toggle_new ();
+
+      gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tv),
+                                                   1, "Can Create Tags",
+                                                   renderer,
+                                                   "active", 2,
+                                                   NULL);
+
+      g_signal_connect (renderer, "toggled",
+                        G_CALLBACK (rich_text_can_create_tags_toggled),
+                        store);
+    }
+
+  sw = gtk_scrolled_window_new (NULL, NULL);
+  gtk_widget_set_size_request (sw, 300, 100);
+  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), sw);
+
+  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), tv);
+
+  hbox = gtk_hbox_new (FALSE, 6);
+  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox,
+                      FALSE, FALSE, 0);
+
+  button = gtk_button_new_with_label ("Unregister Selected Format");
+  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+
+  g_signal_connect (button, "clicked",
+                    G_CALLBACK (rich_text_unregister_clicked),
+                    tv);
+
+  button = gtk_button_new_with_label ("Register New Tagset\n"
+                                      "for the Internal Format");
+  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+
+  g_signal_connect (button, "clicked",
+                    G_CALLBACK (rich_text_register_clicked),
+                    tv);
+
+  if (deserialize)
+    g_signal_connect_object (buffer, "notify::paste-target-list",
+                             G_CALLBACK (rich_text_paste_target_list_notify),
+                             G_OBJECT (store), 0);
+  else
+    g_signal_connect_object (buffer, "notify::copy-target-list",
+                             G_CALLBACK (rich_text_copy_target_list_notify),
+                             G_OBJECT (store), 0);
+
+  rich_text_store_populate (store, buffer, deserialize);
+
+  gtk_widget_show_all (dialog);
+}
+
 enum
 {
   RESPONSE_FORWARD,
@@ -1695,8 +1956,10 @@ static GtkItemFactoryEntry menu_items[] =
   { "/Attributes/Default tabs",          NULL,         do_apply_tabs, TRUE, NULL },
   { "/Attributes/Color cycles",          NULL,         do_apply_colors, TRUE, NULL },
   { "/Attributes/No colors",                     NULL,         do_apply_colors, FALSE, NULL },
-  { "/Attributes/Remove all tags",       NULL, do_remove_tags, 0, NULL },
-  { "/Attributes/Properties",       NULL, do_properties, 0, NULL },
+  { "/Attributes/Remove all tags",        NULL, do_remove_tags, 0, NULL },
+  { "/Attributes/Properties",             NULL, do_properties, 0, NULL },
+  { "/Attributes/Rich Text copy & drag",  NULL, do_rich_text, 0, NULL },
+  { "/Attributes/Rich Text paste & drop", NULL, do_rich_text, 1, NULL },
   { "/_Test",           NULL,         NULL,           0, "<Branch>" },
   { "/Test/_Example",           NULL,         do_example,  0, NULL },
   { "/Test/_Insert and scroll", NULL,         do_insert_and_scroll,  0, NULL },